Compare commits
12 Commits
f37216f4c7
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c1f8d3b28 | |||
| 930dafd314 | |||
| 59f214d956 | |||
| fb3b82d33d | |||
| 1258ea8f61 | |||
| fd5ef6753b | |||
| 2b9c503c9e | |||
| a1a6ba4c34 | |||
| 6bd8d48846 | |||
| f38108d3fa | |||
| fbb19e86aa | |||
| 748141d30c |
16
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get install -y gcc libgomp1
|
||||
|
||||
- name: Build
|
||||
run: make
|
||||
4
.gitignore
vendored
@@ -1,2 +1,2 @@
|
||||
wallpaper
|
||||
distance_field_generator
|
||||
pkl-wallpaper
|
||||
distance_field_generator
|
||||
|
||||
22
Makefile
@@ -1,7 +1,21 @@
|
||||
all: wallpaper distance_field_generator
|
||||
all: pkl-wallpaper distance_field_generator
|
||||
|
||||
wallpaper: src/wallpaper.c
|
||||
gcc -O3 -Werror -Wall -Wextra -I./vendor/raylib-5.5_linux_amd64/include/ -o wallpaper src/wallpaper.c -L./vendor/raylib-5.5_linux_amd64/lib -l:libraylib.a -lm
|
||||
pkl-wallpaper: src/wallpaper.c
|
||||
gcc -O3 -Werror -Wall -Wextra -pedantic -I./vendor/raylib-5.5_linux_amd64/include/ -o pkl-wallpaper src/wallpaper.c -L./vendor/raylib-5.5_linux_amd64/lib -l:libraylib.a -lm
|
||||
|
||||
distance_field_generator: src/distance_field_generator.c
|
||||
gcc -Werror -Wall -Wextra -fopenmp -I./vendor/raylib-5.5_linux_amd64/include/ -o distance_field_generator src/distance_field_generator.c -L./vendor/raylib-5.5_linux_amd64/lib -l:libraylib.a -lm
|
||||
gcc -Werror -Wall -Wextra -pedantic -fopenmp -I./vendor/raylib-5.5_linux_amd64/include/ -o distance_field_generator src/distance_field_generator.c -L./vendor/raylib-5.5_linux_amd64/lib -l:libraylib.a -lm
|
||||
|
||||
install: pkl-wallpaper
|
||||
sudo install -D -m 755 pkl-wallpaper /usr/local/bin/pkl-wallpaper
|
||||
sudo mkdir -p /usr/local/share/pkl-wallpaper
|
||||
sudo cp -r assets /usr/local/share/pkl-wallpaper/
|
||||
|
||||
uninstall:
|
||||
sudo rm -f /usr/local/bin/pkl-wallpaper
|
||||
sudo rm -rf /usr/local/share/pkl-wallpaper/
|
||||
|
||||
clean:
|
||||
rm -f pkl-wallpaper distance_field_generator
|
||||
|
||||
.PHONY: all install uninstall clean
|
||||
|
||||
58
README.md
@@ -1,14 +1,60 @@
|
||||
# wallpaper
|
||||
<div align="center">
|
||||
<h1>pihkaal's wallpaper</h1>
|
||||
<img src="docs/demo.gif" alt="demo" />
|
||||
</div>
|
||||
|
||||
An animated and interactive wallpaper I'm building for myself, designed to run on [Hyprland](https://hyprland.org/).
|
||||
|
||||
## What it does
|
||||
|
||||
- Animated stars
|
||||
- Metaballs-like effect with the "clouds"
|
||||
|
||||
## Limitations
|
||||
|
||||
- The wallpaper will only work on one monitor and on 1920x1080px for now
|
||||
|
||||
## Stack
|
||||
|
||||
- [C](https://en.wikipedia.org/wiki/C_(programming_language))
|
||||
- [Raylib](https://www.raylib.com/)
|
||||
- [Hyprland](https://hypr.land/)
|
||||
|
||||
## Quick start
|
||||
|
||||
### Build and install
|
||||
|
||||
```console
|
||||
$ make
|
||||
|
||||
# generate distance field with 50px search radius
|
||||
# note that i already ship a generated distance field
|
||||
$ ./distance_field_generator ./resources/textures/background_mask.png ./resources/textures/background_distance_field.png 50
|
||||
# generate distance field with 100px ball radius (needed to offset vertically the effect)
|
||||
# NOTE: I already ship a generated distance field
|
||||
# USAGE: distance_field_generator <mask_path> <output_path> <ball_radius>
|
||||
$ ./distance_field_generator ./assets/textures/background_mask.png ./assets/textures/background_distance_field.png 100
|
||||
|
||||
# run wallpaper, image paths are customizables
|
||||
$ ./wallpaper
|
||||
# install
|
||||
$ make install
|
||||
|
||||
# uninstall
|
||||
$ make uninstall
|
||||
```
|
||||
|
||||
### Run as wallpaper
|
||||
|
||||
You'll need to install [hyprwinwrap](https://code.hyprland.org/hyprwm/hyprland-plugins/src/branch/main/hyprwinwrap), then add this to your hyprland configuration:
|
||||
|
||||
```ini
|
||||
plugin {
|
||||
hyprwinwrap {
|
||||
class = pkl-wallpaper
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Run as debug
|
||||
|
||||
Even if you've configured the app to always run as wallpaper using hyprwinwrap, you can still run it in debug mode:
|
||||
|
||||
```console
|
||||
$ DEBUG=1 ./pkl-wallpaper
|
||||
```
|
||||
|
||||
@@ -9,10 +9,9 @@ uniform mat4 mvp;
|
||||
out vec2 fragTexCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
void main() {
|
||||
fragTexCoord = vertexTexCoord;
|
||||
fragColor = vertexColor;
|
||||
|
||||
gl_Position = mvp * vec4(vertexPosition, 1.0);
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,8 @@ uniform float shapeInfluence;
|
||||
out vec4 finalColor;
|
||||
|
||||
float getBallInfluence(vec2 pos, vec2 center, float radius) {
|
||||
vec2 diff = pos - center;
|
||||
float distSq = dot(diff, diff);
|
||||
vec2 diff = pos - center;
|
||||
float distSq = dot(diff, diff);
|
||||
float radiusSq = radius * radius;
|
||||
if (distSq > radiusSq) return 0.0;
|
||||
float dist = sqrt(distSq);
|
||||
@@ -23,38 +23,35 @@ float getBallInfluence(vec2 pos, vec2 center, float radius) {
|
||||
|
||||
float getShapeInfluence(vec2 pos) {
|
||||
vec2 uv = pos / resolution;
|
||||
uv.y = 1 - uv.y + 0.045; // invert y and offset to match with wallpaper texture
|
||||
|
||||
if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0)))) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
uv.y = 1.0 - uv.y;
|
||||
|
||||
if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0)))) return 0.0;
|
||||
|
||||
float signedDistance = texture(distanceFieldTex, uv).r * 255.0 - 128.0;
|
||||
|
||||
float isInside = step(signedDistance, 0.0);
|
||||
|
||||
float isInside = step(signedDistance, 0.0);
|
||||
float isOutside = 1.0 - isInside;
|
||||
|
||||
|
||||
// inside calculation
|
||||
float normalizedDist = abs(signedDistance) * 0.015625; // 1/64
|
||||
float normalizedDist = abs(signedDistance) * 0.015625; // 1/64
|
||||
float insideInfluence = shapeInfluence * exp(-normalizedDist * 0.5) * 3.0;
|
||||
|
||||
|
||||
// outside calculation
|
||||
float adjustedDistance = signedDistance * 0.5;
|
||||
float shapeInfluenceSq = shapeInfluence * shapeInfluence;
|
||||
float outsideInfluence = shapeInfluenceSq / (adjustedDistance * adjustedDistance + shapeInfluence) +
|
||||
float outsideInfluence = shapeInfluenceSq / (adjustedDistance * adjustedDistance + shapeInfluence) +
|
||||
shapeInfluence * 0.8 * exp(-signedDistance * 0.025); // 1/40
|
||||
|
||||
|
||||
return isInside * insideInfluence + isOutside * outsideInfluence;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
void main() {
|
||||
vec2 pixelPos = fragTexCoord * resolution;
|
||||
|
||||
float ballValue = getBallInfluence(pixelPos, mousePos, ballRadius);
|
||||
|
||||
float ballValue = getBallInfluence(pixelPos, mousePos, ballRadius);
|
||||
float shapeValue = getShapeInfluence(pixelPos);
|
||||
float totalValue = ballValue + shapeValue * 0.01;
|
||||
|
||||
float mask = step(0.75, totalValue);
|
||||
finalColor = vec4(mask, mask, mask, 1.0);
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 211 KiB |
BIN
assets/textures/background_distance_field.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 7.9 MiB After Width: | Height: | Size: 7.9 MiB |
BIN
assets/textures/star_1.png
Normal file
|
After Width: | Height: | Size: 386 KiB |
BIN
assets/textures/star_2.png
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
assets/textures/star_3.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/textures/star_4.png
Normal file
|
After Width: | Height: | Size: 386 KiB |
BIN
assets/textures/star_5.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
docs/demo.gif
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 193 KiB |
@@ -1,124 +1,171 @@
|
||||
#include <raylib.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <omp.h>
|
||||
|
||||
bool is_inside_shape(Color pixel)
|
||||
{
|
||||
static bool is_inside_shape(Color pixel) {
|
||||
return pixel.r == 255 && pixel.g == 255 && pixel.b == 255;
|
||||
}
|
||||
|
||||
Image generate_distance_field(Image input_texture, int search_radius)
|
||||
{
|
||||
int width = input_texture.width;
|
||||
int height = input_texture.height;
|
||||
// 1D squared Euclidean distance transform (felzenszwalb & huttenlocher)
|
||||
static void edt_1d(float *f, int n, int *v, float *z, float *tmp) {
|
||||
int k = 0;
|
||||
v[0] = 0;
|
||||
z[0] = -1e30f;
|
||||
z[1] = 1e30f;
|
||||
|
||||
Image distance_field = GenImageColor(width, height, (Color){128, 128, 128, 255});
|
||||
Color *input_pixels = LoadImageColors(input_texture);
|
||||
Color *output_pixels = LoadImageColors(distance_field);
|
||||
|
||||
TraceLog(LOG_INFO, "Using %d threads for parallel processing", omp_get_max_threads());
|
||||
|
||||
#pragma omp parallel for schedule(dynamic, 64)
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
if (omp_get_thread_num() == 0 && y % (height / 10) == 0)
|
||||
{
|
||||
TraceLog(LOG_INFO, "Progress: %d%%", (y * 100) / height);
|
||||
}
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
int index = y * width + x;
|
||||
bool is_inside = is_inside_shape(input_pixels[index]);
|
||||
|
||||
float min_distance_sq = search_radius * search_radius;
|
||||
bool found_boundary = false;
|
||||
|
||||
for (int dy = -search_radius; dy <= search_radius; dy += 1)
|
||||
{
|
||||
for (int dx = -search_radius; dx <= search_radius; dx += 1)
|
||||
{
|
||||
int sample_x = x + dx;
|
||||
int sample_y = y + dy;
|
||||
|
||||
if (sample_x < 0 || sample_x >= width || sample_y < 0 || sample_y >= height) continue;
|
||||
|
||||
int sample_index = sample_y * width + sample_x;
|
||||
bool sample_is_inside = is_inside_shape(input_pixels[sample_index]);
|
||||
|
||||
if (sample_is_inside != is_inside)
|
||||
{
|
||||
float distance_sq = dx * dx + dy * dy;
|
||||
if (distance_sq < min_distance_sq)
|
||||
{
|
||||
min_distance_sq = distance_sq;
|
||||
found_boundary = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float distance = found_boundary ? sqrtf(min_distance_sq) : (float)search_radius;
|
||||
float normalized_distance = distance / (float)search_radius;
|
||||
|
||||
unsigned char distance_value;
|
||||
if (is_inside)
|
||||
{
|
||||
distance_value = (unsigned char)(127.0f - 127.0f * normalized_distance);
|
||||
}
|
||||
else
|
||||
{
|
||||
distance_value = (unsigned char)(128.0f + 127.0f * normalized_distance);
|
||||
}
|
||||
|
||||
output_pixels[index] = (Color){distance_value, distance_value, distance_value, 255};
|
||||
for (int q = 1; q < n; q += 1) {
|
||||
float fq = f[q];
|
||||
float s = ((fq + (float)(q * q)) - (f[v[k]] + (float)(v[k] * v[k]))) / (float)(2 * (q - v[k]));
|
||||
while (s <= z[k]) {
|
||||
k -= 1;
|
||||
s = ((fq + (float)(q * q)) - (f[v[k]] + (float)(v[k] * v[k]))) / (float)(2 * (q - v[k]));
|
||||
}
|
||||
k += 1;
|
||||
v[k] = q;
|
||||
z[k] = s;
|
||||
z[k + 1] = 1e30f;
|
||||
}
|
||||
|
||||
for (int i = 0; i < width * height; i++)
|
||||
{
|
||||
ImageDrawPixel(&distance_field, i % width, i / width, output_pixels[i]);
|
||||
int j = 0;
|
||||
for (int q = 0; q < n; q += 1) {
|
||||
while (z[j + 1] < (float)q) j += 1;
|
||||
float d = (float)(q - v[j]);
|
||||
tmp[q] = d * d + f[v[j]];
|
||||
}
|
||||
|
||||
UnloadImageColors(input_pixels);
|
||||
UnloadImageColors(output_pixels);
|
||||
|
||||
return distance_field;
|
||||
memcpy(f, tmp, n * sizeof(float));
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc < 3)
|
||||
static float *compute_edt(bool *seeds, int width, int height) {
|
||||
float large = (float)((width + height) * (width + height));
|
||||
float *grid = malloc(width * height * sizeof(float));
|
||||
|
||||
for (int i = 0; i < width * height; i += 1) {
|
||||
grid[i] = seeds[i] ? 0.0f : large;
|
||||
}
|
||||
|
||||
int maxdim = width > height ? width : height;
|
||||
|
||||
#pragma omp parallel
|
||||
{
|
||||
TraceLog(LOG_ERROR, "Usage: %s <input_image> <output_image> [search_radius]", argv[0]);
|
||||
int *v = malloc(maxdim * sizeof(int));
|
||||
float *z = malloc((maxdim + 1) * sizeof(float));
|
||||
float *tmp = malloc(maxdim * sizeof(float));
|
||||
float *col = malloc(maxdim * sizeof(float));
|
||||
|
||||
// 1D EDT along each column
|
||||
#pragma omp for nowait
|
||||
for (int x = 0; x < width; x += 1) {
|
||||
for (int y = 0; y < height; y += 1) col[y] = grid[y * width + x];
|
||||
edt_1d(col, height, v, z, tmp);
|
||||
for (int y = 0; y < height; y += 1) grid[y * width + x] = col[y];
|
||||
}
|
||||
|
||||
#pragma omp barrier
|
||||
|
||||
// 1D EDT along each row
|
||||
#pragma omp for nowait
|
||||
for (int y = 0; y < height; y += 1) {
|
||||
memcpy(col, &grid[y * width], width * sizeof(float));
|
||||
edt_1d(col, width, v, z, tmp);
|
||||
memcpy(&grid[y * width], col, width * sizeof(float));
|
||||
}
|
||||
|
||||
free(v);
|
||||
free(z);
|
||||
free(tmp);
|
||||
free(col);
|
||||
}
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
static Image generate_distance_field(Image input_texture, int offset_y) {
|
||||
int width = input_texture.width;
|
||||
int height = input_texture.height;
|
||||
|
||||
Color *input_pixels = LoadImageColors(input_texture);
|
||||
bool *inside_mask = calloc(width * height, sizeof(bool));
|
||||
|
||||
for (int y = 0; y < height; y += 1) {
|
||||
int src_y = y + offset_y;
|
||||
if (src_y < 0 || src_y >= height) continue;
|
||||
for (int x = 0; x < width; x += 1) {
|
||||
inside_mask[y * width + x] = is_inside_shape(input_pixels[src_y * width + x]);
|
||||
}
|
||||
}
|
||||
UnloadImageColors(input_pixels);
|
||||
|
||||
bool *outside_mask = malloc(width * height * sizeof(bool));
|
||||
for (int i = 0; i < width * height; i += 1) outside_mask[i] = !inside_mask[i];
|
||||
|
||||
TraceLog(LOG_INFO, "Computing inside EDT...");
|
||||
float *dist_to_outside = compute_edt(outside_mask, width, height);
|
||||
|
||||
TraceLog(LOG_INFO, "Computing outside EDT...");
|
||||
float *dist_to_inside = compute_edt(inside_mask, width, height);
|
||||
|
||||
free(outside_mask);
|
||||
|
||||
// pixel_value = clamp(sdf, -128, 127) + 128
|
||||
Color *out_pixels = malloc(width * height * sizeof(Color));
|
||||
for (int i = 0; i < width * height; i += 1) {
|
||||
float sdf = inside_mask[i]
|
||||
? -sqrtf(dist_to_outside[i]) // negative = inside
|
||||
: +sqrtf(dist_to_inside[i]); // positive = outside
|
||||
|
||||
float clamped = fmaxf(-128.0f, fminf(127.0f, sdf));
|
||||
unsigned char v = (unsigned char)(clamped + 128.0f);
|
||||
out_pixels[i] = (Color){v, v, v, 255};
|
||||
}
|
||||
|
||||
free(inside_mask);
|
||||
free(dist_to_inside);
|
||||
free(dist_to_outside);
|
||||
|
||||
Image out = {
|
||||
.data = out_pixels,
|
||||
.width = width,
|
||||
.height = height,
|
||||
.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
|
||||
.mipmaps = 1,
|
||||
};
|
||||
return out;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 3) {
|
||||
TraceLog(LOG_ERROR, "Usage: %s <input_image> <output_image> [ball_radius]", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *input_path = argv[1];
|
||||
const char *input_path = argv[1];
|
||||
const char *output_path = argv[2];
|
||||
int search_radius = (argc >= 4) ? atoi(argv[3]) : 100;
|
||||
int ball_radius = (argc >= 4) ? atoi(argv[3]) : 0;
|
||||
int offset_y = (ball_radius + 10) / 2;
|
||||
|
||||
Image input_image = LoadImage(input_path);
|
||||
if (input_image.data == NULL)
|
||||
{
|
||||
TraceLog(LOG_ERROR, "Error: Failed to load input image '%s'", input_path);
|
||||
if (input_image.data == NULL) {
|
||||
TraceLog(LOG_ERROR, "Failed to load input image '%s'", input_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Image distance_field_image = generate_distance_field(input_image, search_radius);
|
||||
if (!ExportImage(distance_field_image, output_path))
|
||||
{
|
||||
TraceLog(LOG_INFO, "Generating distance field (%dx%d, offset_y=%d)...", input_image.width, input_image.height, offset_y);
|
||||
|
||||
Image distance_field = generate_distance_field(input_image, offset_y);
|
||||
|
||||
if (!ExportImage(distance_field, output_path)) {
|
||||
TraceLog(LOG_ERROR, "Failed to save output image '%s'", output_path);
|
||||
UnloadImage(input_image);
|
||||
UnloadImage(distance_field_image);
|
||||
UnloadImage(distance_field);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TraceLog(LOG_INFO, "Distance field saved to '%s'", output_path);
|
||||
|
||||
UnloadImage(input_image);
|
||||
UnloadImage(distance_field_image);
|
||||
UnloadImage(distance_field);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
143
src/wallpaper.c
@@ -6,23 +6,41 @@
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#define WIDTH 1920
|
||||
#define WIDTH 1920
|
||||
#define HEIGHT 1080
|
||||
|
||||
char *hyprland_socket_path;
|
||||
#define BALL_RADIUS 100.0f
|
||||
#define INFLUENCE 200.0f
|
||||
|
||||
Vector2 hyprland_get_cursor_position()
|
||||
{
|
||||
Vector2 pos = {0};
|
||||
typedef struct {
|
||||
const char* name;
|
||||
Vector2 pos;
|
||||
Vector2 origin;
|
||||
float angle;
|
||||
float angle_step;
|
||||
} Star;
|
||||
|
||||
static Star stars[] = {
|
||||
{ .name = "star_1.png", .pos = { 4, 528 }, .origin = {165, 166}, .angle = 90.0, .angle_step = -0.025 },
|
||||
{ .name = "star_2.png", .pos = { 215, 809 }, .origin = {82, 83}, .angle = 90.0, .angle_step = 0.05 },
|
||||
{ .name = "star_3.png", .pos = { 333, 781 }, .origin = {25, 31}, .angle = 90.0, .angle_step = 0.15 },
|
||||
{ .name = "star_4.png", .pos = { 956, 405 }, .origin = {166, 167}, .angle = 90.0, .angle_step = -0.1 },
|
||||
{ .name = "star_5.png", .pos = { 1164, 570 }, .origin = {66, 70}, .angle = 90.0, .angle_step = 0.075 },
|
||||
};
|
||||
|
||||
#define STARS_COUNT (int)(sizeof(stars) / sizeof(stars[0]))
|
||||
|
||||
static char *hyprland_socket_path;
|
||||
|
||||
static Vector2 hyprland_get_cursor_position() {
|
||||
Vector2 pos = {0, 0};
|
||||
|
||||
int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sock_fd == -1)
|
||||
{
|
||||
perror("socket");
|
||||
if (sock_fd == -1) {
|
||||
TraceLog(LOG_ERROR, "socket: %s", strerror(errno));
|
||||
return pos;
|
||||
}
|
||||
|
||||
@@ -30,27 +48,23 @@ Vector2 hyprland_get_cursor_position()
|
||||
addr.sun_family = AF_UNIX;
|
||||
strncpy(addr.sun_path, hyprland_socket_path, sizeof(addr.sun_path) - 1);
|
||||
|
||||
if (connect(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
|
||||
{
|
||||
printf("Using hyprland socket: %s\n", hyprland_socket_path);
|
||||
perror("connect");
|
||||
if (connect(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
|
||||
TraceLog(LOG_ERROR, "connect %s: %s", hyprland_socket_path, strerror(errno));
|
||||
close(sock_fd);
|
||||
return pos;
|
||||
}
|
||||
|
||||
const char *message = "/cursorpos";
|
||||
if (send(sock_fd, message, strlen(message), 0) == -1)
|
||||
{
|
||||
perror("send");
|
||||
if (send(sock_fd, message, strlen(message), 0) == -1) {
|
||||
TraceLog(LOG_ERROR, "send: %s", strerror(errno));
|
||||
close(sock_fd);
|
||||
return pos;
|
||||
}
|
||||
|
||||
char buffer[256] = {0};
|
||||
ssize_t bytes_received = recv(sock_fd, buffer, sizeof(buffer) - 1, 0);
|
||||
if (bytes_received == -1)
|
||||
{
|
||||
perror("recv");
|
||||
if (bytes_received == -1) {
|
||||
TraceLog(LOG_ERROR, "recv: %s", strerror(errno));
|
||||
close(sock_fd);
|
||||
return pos;
|
||||
}
|
||||
@@ -58,67 +72,91 @@ Vector2 hyprland_get_cursor_position()
|
||||
// format is x, y so parse here
|
||||
buffer[bytes_received] = '\0';
|
||||
|
||||
if (sscanf(buffer, "%f, %f", &pos.x, &pos.y) != 2)
|
||||
{
|
||||
fprintf(stderr, "Failed to parse cursor position\n");
|
||||
if (sscanf(buffer, "%f, %f", &pos.x, &pos.y) != 2) {
|
||||
TraceLog(LOG_ERROR, "Failed to parse cursor position");
|
||||
}
|
||||
|
||||
close(sock_fd);
|
||||
return pos;
|
||||
}
|
||||
|
||||
Texture2D load_texture_from_file(const char *file_path)
|
||||
{
|
||||
Texture2D texture = LoadTexture(file_path);
|
||||
if (texture.id == 0)
|
||||
{
|
||||
static const char *resolve_asset_path(const char *path) {
|
||||
if (FileExists(path)) return path;
|
||||
const char *system_path = TextFormat("/usr/local/share/pkl-wallpaper/%s", path);
|
||||
if (FileExists(system_path)) return system_path;
|
||||
return path;
|
||||
}
|
||||
|
||||
static Texture2D load_texture_from_file(const char *file_path) {
|
||||
Texture2D texture = LoadTexture(resolve_asset_path(file_path));
|
||||
if (texture.id == 0) {
|
||||
TraceLog(LOG_ERROR, "Failed to load texture from file: %s", file_path);
|
||||
abort();
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
InitWindow(WIDTH, HEIGHT, "pihkaal-wallpaper");
|
||||
static Shader load_shader_from_file(const char *vs_path, const char *fs_path) {
|
||||
Shader shader = LoadShader(resolve_asset_path(vs_path), resolve_asset_path(fs_path));
|
||||
if (shader.id == 0) {
|
||||
TraceLog(LOG_ERROR, "Failed to load shader from files: %s, %s", vs_path, fs_path);
|
||||
abort();
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
static const char* get_window_title() {
|
||||
return getenv("DEBUG") != NULL ? "pkl-wallpaper-debug" : "pkl-wallpaper";
|
||||
}
|
||||
|
||||
static char* get_hyprland_socket_path() {
|
||||
return strdup(TextFormat("%s/hypr/%s/.socket.sock", getenv("XDG_RUNTIME_DIR"), getenv("HYPRLAND_INSTANCE_SIGNATURE")));
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
InitWindow(WIDTH, HEIGHT, get_window_title());
|
||||
SetWindowState(FLAG_WINDOW_RESIZABLE);
|
||||
SetTargetFPS(60);
|
||||
|
||||
// initialize hyprland socket path
|
||||
hyprland_socket_path = strdup(TextFormat("%s/hypr/%s/.socket.sock", getenv("XDG_RUNTIME_DIR"), getenv("HYPRLAND_INSTANCE_SIGNATURE")));
|
||||
hyprland_socket_path = get_hyprland_socket_path();
|
||||
|
||||
// initialize textures
|
||||
Texture2D distanceFieldTex = load_texture_from_file("resources/textures/background_distance_field.png");
|
||||
Texture2D backgroundTex = load_texture_from_file("resources/textures/background_transparent.png");
|
||||
Texture2D distanceFieldTex = load_texture_from_file("assets/textures/background_distance_field.png");
|
||||
Texture2D backgroundTex = load_texture_from_file("assets/textures/background_transparent.png");
|
||||
Texture2D starTextures[STARS_COUNT] = {0};
|
||||
for (int i = 0; i < STARS_COUNT; i++) {
|
||||
starTextures[i] = load_texture_from_file(TextFormat("assets/textures/%s", stars[i].name));
|
||||
}
|
||||
|
||||
RenderTexture2D target = LoadRenderTexture(WIDTH, HEIGHT);
|
||||
|
||||
// initialize shader
|
||||
Shader shader = LoadShader("resources/shaders/basic.vs", "resources/shaders/distance_field.fs");
|
||||
Shader shader = load_shader_from_file("assets/shaders/basic.vs", "assets/shaders/distance_field.fs");
|
||||
|
||||
int resolutionLoc = GetShaderLocation(shader, "resolution");
|
||||
int mousePosLoc = GetShaderLocation(shader, "mousePos");
|
||||
int ballRadiusLoc = GetShaderLocation(shader, "ballRadius");
|
||||
int shapeInfluenceLoc = GetShaderLocation(shader, "shapeInfluence");
|
||||
int resolutionLoc = GetShaderLocation(shader, "resolution");
|
||||
int mousePosLoc = GetShaderLocation(shader, "mousePos");
|
||||
int ballRadiusLoc = GetShaderLocation(shader, "ballRadius");
|
||||
int shapeInfluenceLoc = GetShaderLocation(shader, "shapeInfluence");
|
||||
int distanceFieldTexLoc = GetShaderLocation(shader, "distanceFieldTex");
|
||||
|
||||
Vector2 resolution = {WIDTH, HEIGHT};
|
||||
SetShaderValue(shader, resolutionLoc, &resolution, SHADER_UNIFORM_VEC2);
|
||||
|
||||
float ballRadius = 100.0f;
|
||||
float ballRadius = BALL_RADIUS;
|
||||
SetShaderValue(shader, ballRadiusLoc, &ballRadius, SHADER_UNIFORM_FLOAT);
|
||||
|
||||
float shapeInfluence = 200.0f;
|
||||
float shapeInfluence = INFLUENCE;
|
||||
SetShaderValue(shader, shapeInfluenceLoc, &shapeInfluence, SHADER_UNIFORM_FLOAT);
|
||||
|
||||
int textureUnit = 1;
|
||||
SetShaderValue(shader, distanceFieldTexLoc, &textureUnit, SHADER_UNIFORM_INT);
|
||||
|
||||
while (!WindowShouldClose())
|
||||
{
|
||||
while (!WindowShouldClose()) {
|
||||
int screenWidth = GetScreenWidth();
|
||||
int screenHeight = GetScreenHeight();
|
||||
|
||||
if (IsWindowResized())
|
||||
{
|
||||
if (IsWindowResized()) {
|
||||
UnloadRenderTexture(target);
|
||||
target = LoadRenderTexture(screenWidth, screenHeight);
|
||||
|
||||
@@ -142,7 +180,15 @@ int main(void)
|
||||
|
||||
DrawTextureRec(backgroundTex, (Rectangle){0, 0, screenWidth, screenHeight}, (Vector2){0, 0}, WHITE);
|
||||
|
||||
DrawText(TextFormat("Distance Field | x: %.0f, y: %.0f | frame: %.2f ms", mousePos.x, mousePos.y, GetFrameTime() * 1000), 10, 10, 20, WHITE);
|
||||
for (int i = 0; i < STARS_COUNT; i++) {
|
||||
Star* star = &stars[i];
|
||||
star->angle = fmodf(star->angle + star->angle_step, 360.0f);
|
||||
|
||||
Texture2D tex = starTextures[i];
|
||||
Rectangle source = {0, 0, (float)tex.width, (float)tex.height};
|
||||
Rectangle dest = {star->pos.x, star->pos.y, (float)tex.width, (float)tex.height};
|
||||
DrawTexturePro(tex, source, dest, star->origin, star->angle, WHITE);
|
||||
}
|
||||
|
||||
EndDrawing();
|
||||
}
|
||||
@@ -150,10 +196,13 @@ int main(void)
|
||||
UnloadRenderTexture(target);
|
||||
UnloadTexture(distanceFieldTex);
|
||||
UnloadTexture(backgroundTex);
|
||||
for (int i = 0; i < STARS_COUNT; i++) {
|
||||
UnloadTexture(starTextures[i]);
|
||||
}
|
||||
UnloadShader(shader);
|
||||
CloseWindow();
|
||||
|
||||
free(hyprland_socket_path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||