2018-10-03 17:14:51 -04:00
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// Copyright (c) 2011-2013, Christopher Jeffrey
|
|
|
|
// Copyright (c) 2013 Richard Grenville <pyxlcy@gmail.com>
|
|
|
|
|
2019-03-10 08:34:37 -04:00
|
|
|
#include <ctype.h>
|
2022-07-17 12:49:35 -04:00
|
|
|
#include <errno.h>
|
|
|
|
#include <fcntl.h>
|
2019-03-30 05:07:21 -04:00
|
|
|
#include <limits.h>
|
2019-01-20 16:15:20 -05:00
|
|
|
#include <math.h>
|
2018-08-22 07:58:49 -04:00
|
|
|
#include <stdbool.h>
|
2022-07-17 12:49:35 -04:00
|
|
|
#include <stdio.h>
|
2019-03-10 08:34:37 -04:00
|
|
|
#include <stdlib.h>
|
2019-01-20 16:15:20 -05:00
|
|
|
#include <string.h>
|
2022-07-17 12:49:35 -04:00
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <unistd.h>
|
2019-03-10 08:34:37 -04:00
|
|
|
#include <xcb/render.h> // for xcb_render_fixed_t, XXX
|
2018-08-22 07:58:49 -04:00
|
|
|
|
2022-07-17 12:49:35 -04:00
|
|
|
#include <test.h>
|
|
|
|
|
2018-08-22 08:26:42 -04:00
|
|
|
#include "c2.h"
|
2019-03-10 08:34:37 -04:00
|
|
|
#include "common.h"
|
|
|
|
#include "compiler.h"
|
|
|
|
#include "kernel.h"
|
2018-12-15 12:53:17 -05:00
|
|
|
#include "log.h"
|
2019-01-20 11:53:39 -05:00
|
|
|
#include "region.h"
|
2019-03-10 08:34:37 -04:00
|
|
|
#include "string_utils.h"
|
2019-01-20 11:53:39 -05:00
|
|
|
#include "types.h"
|
2019-03-10 08:34:37 -04:00
|
|
|
#include "utils.h"
|
2019-01-20 11:53:39 -05:00
|
|
|
#include "win.h"
|
2018-12-15 12:42:37 -05:00
|
|
|
|
|
|
|
#include "config.h"
|
2018-08-22 07:58:49 -04:00
|
|
|
|
2022-07-17 12:49:35 -04:00
|
|
|
const char *xdg_config_home(void) {
|
|
|
|
char *xdgh = getenv("XDG_CONFIG_HOME");
|
|
|
|
char *home = getenv("HOME");
|
|
|
|
const char *default_dir = "/.config";
|
|
|
|
|
|
|
|
if (!xdgh) {
|
|
|
|
if (!home) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
xdgh = cvalloc(strlen(home) + strlen(default_dir) + 1);
|
|
|
|
|
|
|
|
strcpy(xdgh, home);
|
|
|
|
strcat(xdgh, default_dir);
|
|
|
|
} else {
|
|
|
|
xdgh = strdup(xdgh);
|
|
|
|
}
|
|
|
|
|
|
|
|
return xdgh;
|
|
|
|
}
|
|
|
|
|
|
|
|
char **xdg_config_dirs(void) {
|
|
|
|
char *xdgd = getenv("XDG_CONFIG_DIRS");
|
|
|
|
size_t count = 0;
|
|
|
|
|
|
|
|
if (!xdgd) {
|
|
|
|
xdgd = "/etc/xdg";
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; xdgd[i]; i++) {
|
|
|
|
if (xdgd[i] == ':') {
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store the string and the result pointers together so they can be
|
|
|
|
// freed together
|
|
|
|
char **dir_list = cvalloc(sizeof(char *) * (count + 2) + strlen(xdgd) + 1);
|
|
|
|
auto dirs = strcpy((char *)dir_list + sizeof(char *) * (count + 2), xdgd);
|
|
|
|
auto path = dirs;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
|
|
dir_list[i] = path;
|
|
|
|
path = strchr(path, ':');
|
|
|
|
*path = '\0';
|
|
|
|
path++;
|
|
|
|
}
|
|
|
|
dir_list[count] = path;
|
|
|
|
|
|
|
|
size_t fill = 0;
|
|
|
|
for (size_t i = 0; i <= count; i++) {
|
|
|
|
if (dir_list[i][0] == '/') {
|
|
|
|
dir_list[fill] = dir_list[i];
|
|
|
|
fill++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dir_list[fill] = NULL;
|
|
|
|
|
|
|
|
return dir_list;
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE(xdg_config_dirs) {
|
|
|
|
auto old_var = getenv("XDG_CONFIG_DIRS");
|
|
|
|
if (old_var) {
|
|
|
|
old_var = strdup(old_var);
|
|
|
|
}
|
|
|
|
unsetenv("XDG_CONFIG_DIRS");
|
|
|
|
|
|
|
|
auto result = xdg_config_dirs();
|
|
|
|
TEST_STREQUAL(result[0], "/etc/xdg");
|
|
|
|
TEST_EQUAL(result[1], NULL);
|
|
|
|
free(result);
|
|
|
|
|
|
|
|
setenv("XDG_CONFIG_DIRS", ".:.:/etc/xdg:.:/:", 1);
|
|
|
|
result = xdg_config_dirs();
|
|
|
|
TEST_STREQUAL(result[0], "/etc/xdg");
|
|
|
|
TEST_STREQUAL(result[1], "/");
|
|
|
|
TEST_EQUAL(result[2], NULL);
|
|
|
|
free(result);
|
|
|
|
|
|
|
|
setenv("XDG_CONFIG_DIRS", ":", 1);
|
|
|
|
result = xdg_config_dirs();
|
|
|
|
TEST_EQUAL(result[0], NULL);
|
|
|
|
free(result);
|
|
|
|
|
|
|
|
if (old_var) {
|
|
|
|
setenv("XDG_CONFIG_DIRS", old_var, 1);
|
|
|
|
free(old_var);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-22 07:58:49 -04:00
|
|
|
/**
|
|
|
|
* Parse a long number.
|
|
|
|
*/
|
2019-03-10 08:34:37 -04:00
|
|
|
bool parse_long(const char *s, long *dest) {
|
|
|
|
const char *endptr = NULL;
|
|
|
|
long val = strtol(s, (char **)&endptr, 0);
|
|
|
|
if (!endptr || endptr == s) {
|
|
|
|
log_error("Invalid number: %s", s);
|
|
|
|
return false;
|
|
|
|
}
|
2020-11-19 06:17:42 -05:00
|
|
|
while (isspace((unsigned char)*endptr))
|
2019-03-10 08:34:37 -04:00
|
|
|
++endptr;
|
|
|
|
if (*endptr) {
|
|
|
|
log_error("Trailing characters: %s", s);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
*dest = val;
|
|
|
|
return true;
|
2018-08-22 07:58:49 -04:00
|
|
|
}
|
|
|
|
|
2019-03-30 05:07:21 -04:00
|
|
|
/**
|
|
|
|
* Parse an int number.
|
|
|
|
*/
|
|
|
|
bool parse_int(const char *s, int *dest) {
|
|
|
|
long val;
|
|
|
|
if (!parse_long(s, &val)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (val > INT_MAX || val < INT_MIN) {
|
|
|
|
log_error("Number exceeded int limits: %ld", val);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
*dest = (int)val;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-08-22 07:58:49 -04:00
|
|
|
/**
|
2019-02-17 18:47:46 -05:00
|
|
|
* Parse a floating-point number in from a string,
|
|
|
|
* also strips the trailing space and comma after the number.
|
|
|
|
*
|
|
|
|
* @param[in] src string to parse
|
|
|
|
* @param[out] dest return the number parsed from the string
|
|
|
|
* @return pointer to the last character parsed
|
2018-08-22 07:58:49 -04:00
|
|
|
*/
|
2019-03-10 08:34:37 -04:00
|
|
|
const char *parse_readnum(const char *src, double *dest) {
|
|
|
|
const char *pc = NULL;
|
|
|
|
double val = strtod_simple(src, &pc);
|
|
|
|
if (!pc || pc == src) {
|
|
|
|
log_error("No number found: %s", src);
|
|
|
|
return src;
|
|
|
|
}
|
2020-11-19 06:17:42 -05:00
|
|
|
while (*pc && (isspace((unsigned char)*pc) || *pc == ',')) {
|
2019-03-10 08:34:37 -04:00
|
|
|
++pc;
|
|
|
|
}
|
|
|
|
*dest = val;
|
|
|
|
return pc;
|
2018-08-22 07:58:49 -04:00
|
|
|
}
|
|
|
|
|
2019-05-31 19:39:00 -04:00
|
|
|
enum blur_method parse_blur_method(const char *src) {
|
|
|
|
if (strcmp(src, "kernel") == 0) {
|
|
|
|
return BLUR_METHOD_KERNEL;
|
|
|
|
} else if (strcmp(src, "box") == 0) {
|
|
|
|
return BLUR_METHOD_BOX;
|
|
|
|
} else if (strcmp(src, "gaussian") == 0) {
|
|
|
|
return BLUR_METHOD_GAUSSIAN;
|
2019-12-20 14:31:00 -05:00
|
|
|
} else if (strcmp(src, "dual_kawase") == 0) {
|
|
|
|
return BLUR_METHOD_DUAL_KAWASE;
|
|
|
|
} else if (strcmp(src, "kawase") == 0) {
|
|
|
|
log_warn("Blur method 'kawase' has been renamed to 'dual_kawase'. "
|
|
|
|
"Interpreted as 'dual_kawase', but this will stop working "
|
|
|
|
"soon.");
|
|
|
|
return BLUR_METHOD_DUAL_KAWASE;
|
2019-05-31 19:39:00 -04:00
|
|
|
} else if (strcmp(src, "none") == 0) {
|
|
|
|
return BLUR_METHOD_NONE;
|
|
|
|
}
|
|
|
|
return BLUR_METHOD_INVALID;
|
|
|
|
}
|
|
|
|
|
2018-08-22 07:58:49 -04:00
|
|
|
/**
|
|
|
|
* Parse a matrix.
|
2019-02-17 18:47:46 -05:00
|
|
|
*
|
|
|
|
* @param[in] src the blur kernel string
|
|
|
|
* @param[out] endptr return where the end of kernel is in the string
|
|
|
|
* @param[out] hasneg whether the kernel has negative values
|
2018-08-22 07:58:49 -04:00
|
|
|
*/
|
2019-03-10 08:34:37 -04:00
|
|
|
conv *parse_blur_kern(const char *src, const char **endptr, bool *hasneg) {
|
|
|
|
int width = 0, height = 0;
|
|
|
|
*hasneg = false;
|
|
|
|
|
|
|
|
const char *pc = NULL;
|
|
|
|
|
|
|
|
// Get matrix width and height
|
|
|
|
double val = 0.0;
|
|
|
|
if (src == (pc = parse_readnum(src, &val)))
|
|
|
|
goto err1;
|
|
|
|
src = pc;
|
2019-03-30 05:07:21 -04:00
|
|
|
width = (int)val;
|
2019-03-10 08:34:37 -04:00
|
|
|
if (src == (pc = parse_readnum(src, &val)))
|
|
|
|
goto err1;
|
|
|
|
src = pc;
|
2019-03-30 05:07:21 -04:00
|
|
|
height = (int)val;
|
2019-03-10 08:34:37 -04:00
|
|
|
|
|
|
|
// Validate matrix width and height
|
|
|
|
if (width <= 0 || height <= 0) {
|
|
|
|
log_error("Blue kernel width/height can't be negative.");
|
|
|
|
goto err1;
|
|
|
|
}
|
|
|
|
if (!(width % 2 && height % 2)) {
|
|
|
|
log_error("Blur kernel width/height must be odd.");
|
|
|
|
goto err1;
|
|
|
|
}
|
|
|
|
if (width > 16 || height > 16)
|
|
|
|
log_warn("Blur kernel width/height too large, may slow down"
|
|
|
|
"rendering, and/or consume lots of memory");
|
|
|
|
|
|
|
|
// Allocate memory
|
2019-03-30 05:07:21 -04:00
|
|
|
conv *matrix = cvalloc(sizeof(conv) + (size_t)(width * height) * sizeof(double));
|
2019-03-10 08:34:37 -04:00
|
|
|
|
|
|
|
// Read elements
|
|
|
|
int skip = height / 2 * width + width / 2;
|
|
|
|
for (int i = 0; i < width * height; ++i) {
|
|
|
|
// Ignore the center element
|
|
|
|
if (i == skip) {
|
2019-06-08 20:01:53 -04:00
|
|
|
matrix->data[i] = 1;
|
2019-03-10 08:34:37 -04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (src == (pc = parse_readnum(src, &val))) {
|
|
|
|
goto err2;
|
|
|
|
}
|
|
|
|
src = pc;
|
|
|
|
if (val < 0) {
|
|
|
|
*hasneg = true;
|
|
|
|
}
|
|
|
|
matrix->data[i] = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Detect trailing characters
|
|
|
|
for (; *pc && *pc != ';'; pc++) {
|
2020-11-19 06:17:42 -05:00
|
|
|
if (!isspace((unsigned char)*pc) && *pc != ',') {
|
2020-08-30 10:25:58 -04:00
|
|
|
// TODO(yshui) isspace is locale aware, be careful
|
2019-03-10 08:34:37 -04:00
|
|
|
log_error("Trailing characters in blur kernel string.");
|
|
|
|
goto err2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Jump over spaces after ';'
|
|
|
|
if (*pc == ';') {
|
|
|
|
pc++;
|
2020-11-19 06:17:42 -05:00
|
|
|
while (*pc && isspace((unsigned char)*pc)) {
|
2019-03-10 08:34:37 -04:00
|
|
|
++pc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Require an end of string if endptr is not provided, otherwise
|
|
|
|
// copy end pointer to endptr
|
|
|
|
if (endptr) {
|
|
|
|
*endptr = pc;
|
|
|
|
} else if (*pc) {
|
|
|
|
log_error("Only one blur kernel expected.");
|
|
|
|
goto err2;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fill in width and height
|
|
|
|
matrix->w = width;
|
|
|
|
matrix->h = height;
|
|
|
|
return matrix;
|
2018-08-22 07:58:49 -04:00
|
|
|
|
2018-12-15 13:47:21 -05:00
|
|
|
err2:
|
2019-03-10 08:34:37 -04:00
|
|
|
free(matrix);
|
2018-12-15 13:47:21 -05:00
|
|
|
err1:
|
2019-03-10 08:34:37 -04:00
|
|
|
return NULL;
|
2018-08-22 07:58:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse a list of convolution kernels.
|
2018-12-21 11:25:28 -05:00
|
|
|
*
|
2019-02-17 18:47:46 -05:00
|
|
|
* @param[in] src string to parse
|
|
|
|
* @param[out] hasneg whether any of the kernels have negative values
|
2019-06-06 02:37:48 -04:00
|
|
|
* @return the kernels
|
2018-08-22 07:58:49 -04:00
|
|
|
*/
|
2019-06-07 16:53:23 -04:00
|
|
|
struct conv **parse_blur_kern_lst(const char *src, bool *hasneg, int *count) {
|
2020-08-30 10:25:58 -04:00
|
|
|
// TODO(yshui) just return a predefined kernels, not parse predefined strings...
|
2019-03-10 08:34:37 -04:00
|
|
|
static const struct {
|
|
|
|
const char *name;
|
|
|
|
const char *kern_str;
|
|
|
|
} CONV_KERN_PREDEF[] = {
|
|
|
|
{"3x3box", "3,3,1,1,1,1,1,1,1,1,"},
|
|
|
|
{"5x5box", "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,"},
|
|
|
|
{"7x7box", "7,7,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,"
|
|
|
|
"1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,"},
|
|
|
|
{"3x3gaussian", "3,3,0.243117,0.493069,0.243117,0.493069,0.493069,0.243117,0."
|
|
|
|
"493069,0.243117,"},
|
|
|
|
{"5x5gaussian", "5,5,0.003493,0.029143,0.059106,0.029143,0.003493,0.029143,0."
|
|
|
|
"243117,0.493069,0.243117,0.029143,0.059106,0.493069,0."
|
|
|
|
"493069,0.059106,0.029143,0.243117,0.493069,0.243117,0."
|
|
|
|
"029143,0.003493,0.029143,0.059106,0.029143,0.003493,"},
|
|
|
|
{"7x7gaussian", "7,7,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0."
|
|
|
|
"000003,0.000102,0.003493,0.029143,0.059106,0.029143,0."
|
|
|
|
"003493,0.000102,0.000849,0.029143,0.243117,0.493069,0."
|
|
|
|
"243117,0.029143,0.000849,0.001723,0.059106,0.493069,0."
|
|
|
|
"493069,0.059106,0.001723,0.000849,0.029143,0.243117,0."
|
|
|
|
"493069,0.243117,0.029143,0.000849,0.000102,0.003493,0."
|
|
|
|
"029143,0.059106,0.029143,0.003493,0.000102,0.000003,0."
|
|
|
|
"000102,0.000849,0.001723,0.000849,0.000102,0.000003,"},
|
|
|
|
{"9x9gaussian",
|
|
|
|
"9,9,0.000000,0.000000,0.000001,0.000006,0.000012,0.000006,0.000001,0."
|
|
|
|
"000000,0.000000,0.000000,0.000003,0.000102,0.000849,0.001723,0.000849,0."
|
|
|
|
"000102,0.000003,0.000000,0.000001,0.000102,0.003493,0.029143,0.059106,0."
|
|
|
|
"029143,0.003493,0.000102,0.000001,0.000006,0.000849,0.029143,0.243117,0."
|
|
|
|
"493069,0.243117,0.029143,0.000849,0.000006,0.000012,0.001723,0.059106,0."
|
|
|
|
"493069,0.493069,0.059106,0.001723,0.000012,0.000006,0.000849,0.029143,0."
|
|
|
|
"243117,0.493069,0.243117,0.029143,0.000849,0.000006,0.000001,0.000102,0."
|
|
|
|
"003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000001,0.000000,0."
|
|
|
|
"000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0.000000,0."
|
|
|
|
"000000,0.000000,0.000001,0.000006,0.000012,0.000006,0.000001,0.000000,0."
|
|
|
|
"000000,"},
|
|
|
|
{"11x11gaussian",
|
|
|
|
"11,11,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0."
|
|
|
|
"000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000001,0."
|
|
|
|
"000006,0.000012,0.000006,0.000001,0.000000,0.000000,0.000000,0.000000,0."
|
|
|
|
"000000,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0."
|
|
|
|
"000000,0.000000,0.000000,0.000001,0.000102,0.003493,0.029143,0.059106,0."
|
|
|
|
"029143,0.003493,0.000102,0.000001,0.000000,0.000000,0.000006,0.000849,0."
|
|
|
|
"029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.000006,0.000000,0."
|
|
|
|
"000000,0.000012,0.001723,0.059106,0.493069,0.493069,0.059106,0.001723,0."
|
|
|
|
"000012,0.000000,0.000000,0.000006,0.000849,0.029143,0.243117,0.493069,0."
|
|
|
|
"243117,0.029143,0.000849,0.000006,0.000000,0.000000,0.000001,0.000102,0."
|
|
|
|
"003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000001,0.000000,0."
|
|
|
|
"000000,0.000000,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0."
|
|
|
|
"000003,0.000000,0.000000,0.000000,0.000000,0.000000,0.000001,0.000006,0."
|
|
|
|
"000012,0.000006,0.000001,0.000000,0.000000,0.000000,0.000000,0.000000,0."
|
|
|
|
"000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0."
|
|
|
|
"000000,"},
|
|
|
|
};
|
|
|
|
|
2019-06-07 16:53:23 -04:00
|
|
|
*count = 0;
|
2019-03-10 08:34:37 -04:00
|
|
|
*hasneg = false;
|
|
|
|
for (unsigned int i = 0;
|
|
|
|
i < sizeof(CONV_KERN_PREDEF) / sizeof(CONV_KERN_PREDEF[0]); ++i) {
|
|
|
|
if (!strcmp(CONV_KERN_PREDEF[i].name, src))
|
2019-06-07 16:53:23 -04:00
|
|
|
return parse_blur_kern_lst(CONV_KERN_PREDEF[i].kern_str, hasneg, count);
|
2019-03-10 08:34:37 -04:00
|
|
|
}
|
|
|
|
|
2019-06-06 02:37:48 -04:00
|
|
|
int nkernels = 1;
|
|
|
|
for (int i = 0; src[i]; i++) {
|
|
|
|
if (src[i] == ';') {
|
|
|
|
nkernels++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-07 16:53:23 -04:00
|
|
|
struct conv **ret = ccalloc(nkernels, struct conv *);
|
2019-06-06 02:37:48 -04:00
|
|
|
|
2019-03-10 08:34:37 -04:00
|
|
|
int i = 0;
|
|
|
|
const char *pc = src;
|
|
|
|
|
|
|
|
// Continue parsing until the end of source string
|
|
|
|
i = 0;
|
2019-06-06 02:37:48 -04:00
|
|
|
while (pc && *pc) {
|
2019-03-10 08:34:37 -04:00
|
|
|
bool tmp_hasneg;
|
2019-06-06 02:37:48 -04:00
|
|
|
assert(i < nkernels);
|
|
|
|
ret[i] = parse_blur_kern(pc, &pc, &tmp_hasneg);
|
|
|
|
if (!ret[i]) {
|
|
|
|
for (int j = 0; j < i; j++) {
|
|
|
|
free(ret[j]);
|
|
|
|
}
|
|
|
|
free(ret);
|
|
|
|
return NULL;
|
2019-03-10 08:34:37 -04:00
|
|
|
}
|
|
|
|
i++;
|
|
|
|
*hasneg |= tmp_hasneg;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i > 1) {
|
2019-04-04 04:26:53 -04:00
|
|
|
log_warn("You are seeing this message because you are using "
|
|
|
|
"multipass blur. Please report an issue to us so we know "
|
|
|
|
"multipass blur is actually been used. Otherwise it might be "
|
|
|
|
"removed in future releases");
|
2019-03-10 08:34:37 -04:00
|
|
|
}
|
|
|
|
|
2019-06-07 16:53:23 -04:00
|
|
|
*count = i;
|
|
|
|
|
2019-06-06 02:37:48 -04:00
|
|
|
return ret;
|
2018-08-22 07:58:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse a X geometry.
|
Convert XfixesRegion to pixman region
Re-did the painting logic, and document it.
It is unclear to me what is the previous painting logic. But the current
one is basically this:
1. Go through all windows top to bottom, and put visible windows (not
unmapped, opacity > 0, etc) into a linked list, from bottom to top
2. Accumulate a region of ignore on each window, which is basically the
region of screen that is obscured by all the windows above current
one.
3. Paint all the visible windows from bottom to top. Subtract the region
of ignore from the painting region. If we need to paint shadow, we
subtract the body of the window from the shadow painting region too,
because we don't want shadow behind the window.
4. region of ignore is invalidated when window stack change, an
window on top moved or changed shape, when window changed between
opaque and transparent, etc.
Notes:
It is unclear whether all the different shapes of a window (extents,
noframe, border, bounding shape, etc) are calculated correctly or not.
It is unclear if window shape related events are handled correctly or
not. Need more testing.
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2018-09-29 23:56:00 -04:00
|
|
|
*
|
|
|
|
* ps->root_width and ps->root_height must be valid
|
2018-08-22 07:58:49 -04:00
|
|
|
*/
|
2019-03-10 08:34:37 -04:00
|
|
|
bool parse_geometry(session_t *ps, const char *src, region_t *dest) {
|
|
|
|
pixman_region32_clear(dest);
|
2019-03-30 05:07:21 -04:00
|
|
|
if (!src) {
|
2019-03-10 08:34:37 -04:00
|
|
|
return true;
|
2019-03-30 05:07:21 -04:00
|
|
|
}
|
|
|
|
if (!ps->root_width || !ps->root_height) {
|
2019-03-10 08:34:37 -04:00
|
|
|
return true;
|
2019-03-30 05:07:21 -04:00
|
|
|
}
|
2019-03-10 08:34:37 -04:00
|
|
|
|
2019-03-30 05:07:21 -04:00
|
|
|
long x = 0, y = 0;
|
|
|
|
long width = ps->root_width, height = ps->root_height;
|
2019-03-10 08:34:37 -04:00
|
|
|
long val = 0L;
|
|
|
|
char *endptr = NULL;
|
|
|
|
|
|
|
|
src = skip_space(src);
|
2019-03-30 05:07:21 -04:00
|
|
|
if (!*src) {
|
2019-03-10 08:34:37 -04:00
|
|
|
goto parse_geometry_end;
|
2019-03-30 05:07:21 -04:00
|
|
|
}
|
2019-03-10 08:34:37 -04:00
|
|
|
|
|
|
|
// Parse width
|
|
|
|
// Must be base 10, because "0x0..." may appear
|
2019-03-30 05:07:21 -04:00
|
|
|
if (*src != '+' && *src != '-') {
|
2019-03-10 08:34:37 -04:00
|
|
|
val = strtol(src, &endptr, 10);
|
|
|
|
assert(endptr);
|
|
|
|
if (src != endptr) {
|
2019-03-30 05:07:21 -04:00
|
|
|
if (val < 0) {
|
2019-03-10 08:34:37 -04:00
|
|
|
log_error("Invalid width: %s", src);
|
|
|
|
return false;
|
|
|
|
}
|
2019-03-30 05:07:21 -04:00
|
|
|
width = val;
|
2019-03-10 08:34:37 -04:00
|
|
|
src = endptr;
|
|
|
|
}
|
|
|
|
src = skip_space(src);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse height
|
2019-03-30 05:07:21 -04:00
|
|
|
if (*src == 'x') {
|
2019-03-10 08:34:37 -04:00
|
|
|
++src;
|
|
|
|
val = strtol(src, &endptr, 10);
|
|
|
|
assert(endptr);
|
|
|
|
if (src != endptr) {
|
2019-03-30 05:07:21 -04:00
|
|
|
if (val < 0) {
|
2019-03-10 08:34:37 -04:00
|
|
|
log_error("Invalid height: %s", src);
|
|
|
|
return false;
|
|
|
|
}
|
2019-03-30 05:07:21 -04:00
|
|
|
height = val;
|
2019-03-10 08:34:37 -04:00
|
|
|
src = endptr;
|
|
|
|
}
|
|
|
|
src = skip_space(src);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse x
|
2019-03-30 05:07:21 -04:00
|
|
|
if (*src == '+' || *src == '-') {
|
2019-03-10 08:34:37 -04:00
|
|
|
val = strtol(src, &endptr, 10);
|
|
|
|
if (endptr && src != endptr) {
|
2019-03-30 05:07:21 -04:00
|
|
|
x = val;
|
|
|
|
if (*src == '-') {
|
|
|
|
x += ps->root_width - width;
|
|
|
|
}
|
2019-03-10 08:34:37 -04:00
|
|
|
src = endptr;
|
|
|
|
}
|
|
|
|
src = skip_space(src);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse y
|
2019-03-30 05:07:21 -04:00
|
|
|
if (*src == '+' || *src == '-') {
|
2019-03-10 08:34:37 -04:00
|
|
|
val = strtol(src, &endptr, 10);
|
|
|
|
if (endptr && src != endptr) {
|
2019-03-30 05:07:21 -04:00
|
|
|
y = val;
|
|
|
|
if (*src == '-') {
|
|
|
|
y += ps->root_height - height;
|
|
|
|
}
|
2019-03-10 08:34:37 -04:00
|
|
|
src = endptr;
|
|
|
|
}
|
|
|
|
src = skip_space(src);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*src) {
|
|
|
|
log_error("Trailing characters: %s", src);
|
|
|
|
return false;
|
|
|
|
}
|
2018-08-22 07:58:49 -04:00
|
|
|
|
|
|
|
parse_geometry_end:
|
2019-03-30 05:07:21 -04:00
|
|
|
if (x < INT_MIN || x > INT_MAX || y < INT_MIN || y > INT_MAX) {
|
|
|
|
log_error("Geometry coordinates exceeded limits: %s", src);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (width > UINT_MAX || height > UINT_MAX) {
|
|
|
|
// less than 0 is checked for earlier
|
|
|
|
log_error("Geometry size exceeded limits: %s", src);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
pixman_region32_union_rect(dest, dest, (int)x, (int)y, (uint)width, (uint)height);
|
2019-03-10 08:34:37 -04:00
|
|
|
return true;
|
2018-08-22 07:58:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse a list of opacity rules.
|
|
|
|
*/
|
2018-12-21 11:25:28 -05:00
|
|
|
bool parse_rule_opacity(c2_lptr_t **res, const char *src) {
|
2019-03-10 08:34:37 -04:00
|
|
|
// Find opacity value
|
|
|
|
char *endptr = NULL;
|
|
|
|
long val = strtol(src, &endptr, 0);
|
|
|
|
if (!endptr || endptr == src) {
|
|
|
|
log_error("No opacity specified: %s", src);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (val > 100 || val < 0) {
|
|
|
|
log_error("Opacity %ld invalid: %s", val, src);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skip over spaces
|
2020-11-19 06:17:42 -05:00
|
|
|
while (*endptr && isspace((unsigned char)*endptr))
|
2019-03-10 08:34:37 -04:00
|
|
|
++endptr;
|
|
|
|
if (':' != *endptr) {
|
|
|
|
log_error("Opacity terminator not found: %s", src);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
++endptr;
|
|
|
|
|
|
|
|
// Parse pattern
|
|
|
|
// I hope 1-100 is acceptable for (void *)
|
|
|
|
return c2_parse(res, endptr, (void *)val);
|
2018-08-22 07:58:49 -04:00
|
|
|
}
|
|
|
|
|
2022-07-17 12:49:35 -04:00
|
|
|
/// Search for auxiliary file under a base directory
|
|
|
|
static char *locate_auxiliary_file_at(const char *base, const char *scope, const char *file) {
|
|
|
|
scoped_charp path = mstrjoin(base, scope);
|
|
|
|
mstrextend(&path, "/");
|
|
|
|
mstrextend(&path, file);
|
|
|
|
if (access(path, O_RDONLY) == 0) {
|
|
|
|
// Canonicalize path to avoid duplicates
|
|
|
|
char *abspath = realpath(path, NULL);
|
|
|
|
return abspath;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a path of an auxiliary file to read, could be a shader file, or any supplimenrary
|
|
|
|
* file.
|
|
|
|
*
|
|
|
|
* Follows the XDG specification to search for the shader file in configuration locations.
|
|
|
|
*
|
|
|
|
* The search order is:
|
|
|
|
* 1) If an absolute path is given, use it directly.
|
|
|
|
* 2) Search for the file directly under `include_dir`.
|
|
|
|
* 3) Search for the file in the XDG configuration directories, under path
|
|
|
|
* /picom/<scope>/
|
|
|
|
*/
|
|
|
|
char *locate_auxiliary_file(const char *scope, const char *path, const char *include_dir) {
|
|
|
|
if (!path || strlen(path) == 0) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filename is absolute path, so try to load from there
|
|
|
|
if (path[0] == '/') {
|
|
|
|
if (access(path, O_RDONLY) == 0) {
|
|
|
|
return realpath(path, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// First try to load file from the include directory (i.e. relative to the
|
|
|
|
// config file)
|
|
|
|
if (include_dir && strlen(include_dir)) {
|
|
|
|
char *ret = locate_auxiliary_file_at(include_dir, "", path);
|
|
|
|
if (ret) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fall back to searching in user config directory
|
|
|
|
scoped_charp picom_scope = mstrjoin("/picom/", scope);
|
|
|
|
scoped_charp config_home = (char *)xdg_config_home();
|
|
|
|
char *ret = locate_auxiliary_file_at(config_home, picom_scope, path);
|
|
|
|
if (ret) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fall back to searching in system config directory
|
|
|
|
auto config_dirs = xdg_config_dirs();
|
|
|
|
for (int i = 0; config_dirs[i]; i++) {
|
|
|
|
ret = locate_auxiliary_file_at(config_dirs[i], picom_scope, path);
|
|
|
|
if (ret) {
|
|
|
|
free(config_dirs);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
free(config_dirs);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse a list of window shader rules.
|
|
|
|
*/
|
|
|
|
bool parse_rule_window_shader(c2_lptr_t **res, const char *src, const char *include_dir) {
|
|
|
|
if (!src) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find custom shader terminator
|
|
|
|
const char *endptr = strchr(src, ':');
|
|
|
|
if (!endptr) {
|
|
|
|
log_error("Custom shader terminator not found: %s", src);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse and create custom shader
|
|
|
|
scoped_charp untrimed_shader_source = strdup(src);
|
|
|
|
if (!untrimed_shader_source) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
auto source_end = strchr(untrimed_shader_source, ':');
|
|
|
|
*source_end = '\0';
|
|
|
|
|
|
|
|
size_t length;
|
|
|
|
char *tmp = (char *)trim_both(untrimed_shader_source, &length);
|
|
|
|
tmp[length] = '\0';
|
|
|
|
char *shader_source = NULL;
|
|
|
|
|
|
|
|
if (strcasecmp(tmp, "default") != 0) {
|
|
|
|
shader_source = locate_auxiliary_file("shaders", tmp, include_dir);
|
|
|
|
if (!shader_source) {
|
|
|
|
log_error("Custom shader file \"%s\" not found for rule: %s", tmp, src);
|
|
|
|
free(shader_source);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return c2_parse(res, ++endptr, (void *)shader_source);
|
|
|
|
}
|
|
|
|
|
2018-08-22 07:58:49 -04:00
|
|
|
/**
|
|
|
|
* Add a pattern to a condition linked list.
|
|
|
|
*/
|
2019-03-10 08:34:37 -04:00
|
|
|
bool condlst_add(c2_lptr_t **pcondlst, const char *pattern) {
|
|
|
|
if (!pattern)
|
|
|
|
return false;
|
2018-08-22 07:58:49 -04:00
|
|
|
|
2019-03-10 08:34:37 -04:00
|
|
|
if (!c2_parse(pcondlst, pattern, NULL))
|
|
|
|
exit(1);
|
2018-08-22 07:58:49 -04:00
|
|
|
|
2019-03-10 08:34:37 -04:00
|
|
|
return true;
|
2018-08-22 07:58:49 -04:00
|
|
|
}
|
2018-12-04 11:28:19 -05:00
|
|
|
|
2019-03-10 08:34:37 -04:00
|
|
|
void set_default_winopts(options_t *opt, win_option_mask_t *mask, bool shadow_enable,
|
2020-10-01 16:51:15 -04:00
|
|
|
bool fading_enable, bool blur_enable) {
|
2019-03-10 08:34:37 -04:00
|
|
|
// Apply default wintype options.
|
|
|
|
if (!mask[WINTYPE_DESKTOP].shadow) {
|
|
|
|
// Desktop windows are always drawn without shadow by default.
|
|
|
|
mask[WINTYPE_DESKTOP].shadow = true;
|
|
|
|
opt->wintype_option[WINTYPE_DESKTOP].shadow = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Focused/unfocused state only apply to a few window types, all other windows
|
|
|
|
// are always considered focused.
|
|
|
|
const wintype_t nofocus_type[] = {WINTYPE_UNKNOWN, WINTYPE_NORMAL, WINTYPE_UTILITY};
|
|
|
|
for (unsigned long i = 0; i < ARR_SIZE(nofocus_type); i++) {
|
|
|
|
if (!mask[nofocus_type[i]].focus) {
|
|
|
|
mask[nofocus_type[i]].focus = true;
|
|
|
|
opt->wintype_option[nofocus_type[i]].focus = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (unsigned long i = 0; i < NUM_WINTYPES; i++) {
|
|
|
|
if (!mask[i].shadow) {
|
|
|
|
mask[i].shadow = true;
|
|
|
|
opt->wintype_option[i].shadow = shadow_enable;
|
|
|
|
}
|
|
|
|
if (!mask[i].fade) {
|
|
|
|
mask[i].fade = true;
|
|
|
|
opt->wintype_option[i].fade = fading_enable;
|
|
|
|
}
|
|
|
|
if (!mask[i].focus) {
|
|
|
|
mask[i].focus = true;
|
|
|
|
opt->wintype_option[i].focus = true;
|
|
|
|
}
|
2020-10-01 16:51:15 -04:00
|
|
|
if (!mask[i].blur_background) {
|
|
|
|
mask[i].blur_background = true;
|
|
|
|
opt->wintype_option[i].blur_background = blur_enable;
|
|
|
|
}
|
2019-03-10 08:34:37 -04:00
|
|
|
if (!mask[i].full_shadow) {
|
|
|
|
mask[i].full_shadow = true;
|
|
|
|
opt->wintype_option[i].full_shadow = false;
|
|
|
|
}
|
|
|
|
if (!mask[i].redir_ignore) {
|
|
|
|
mask[i].redir_ignore = true;
|
|
|
|
opt->wintype_option[i].redir_ignore = false;
|
|
|
|
}
|
|
|
|
if (!mask[i].opacity) {
|
|
|
|
mask[i].opacity = true;
|
|
|
|
// Opacity is not set to a concrete number here because the
|
|
|
|
// opacity logic is complicated, and needs an "unset" state
|
|
|
|
opt->wintype_option[i].opacity = NAN;
|
|
|
|
}
|
2021-07-09 13:17:23 -04:00
|
|
|
if (!mask[i].clip_shadow_above) {
|
|
|
|
mask[i].clip_shadow_above = true;
|
|
|
|
opt->wintype_option[i].clip_shadow_above = false;
|
|
|
|
}
|
2019-03-10 08:34:37 -04:00
|
|
|
}
|
2018-12-24 13:28:00 -05:00
|
|
|
}
|
|
|
|
|
2019-03-10 08:34:37 -04:00
|
|
|
char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
|
|
|
|
bool *fading_enable, bool *hasneg, win_option_mask_t *winopt_mask) {
|
2020-09-05 10:46:31 -04:00
|
|
|
// clang-format off
|
2019-05-27 17:59:56 -04:00
|
|
|
*opt = (struct options){
|
|
|
|
.backend = BKEND_XRENDER,
|
2022-08-21 01:00:43 -04:00
|
|
|
.legacy_backends = false,
|
2019-05-27 17:59:56 -04:00
|
|
|
.glx_no_stencil = false,
|
|
|
|
.mark_wmwin_focused = false,
|
|
|
|
.mark_ovredir_focused = false,
|
|
|
|
.detect_rounded_corners = false,
|
|
|
|
.resize_damage = 0,
|
|
|
|
.unredir_if_possible = false,
|
|
|
|
.unredir_if_possible_blacklist = NULL,
|
|
|
|
.unredir_if_possible_delay = 0,
|
|
|
|
.redirected_force = UNSET,
|
|
|
|
.stoppaint_force = UNSET,
|
|
|
|
.dbus = false,
|
|
|
|
.benchmark = 0,
|
|
|
|
.benchmark_wid = XCB_NONE,
|
|
|
|
.logpath = NULL,
|
|
|
|
|
2019-09-28 19:48:01 -04:00
|
|
|
.use_damage = true,
|
2019-05-27 17:59:56 -04:00
|
|
|
|
|
|
|
.shadow_red = 0.0,
|
|
|
|
.shadow_green = 0.0,
|
|
|
|
.shadow_blue = 0.0,
|
|
|
|
.shadow_radius = 18,
|
|
|
|
.shadow_offset_x = -15,
|
|
|
|
.shadow_offset_y = -15,
|
|
|
|
.shadow_opacity = .75,
|
|
|
|
.shadow_blacklist = NULL,
|
|
|
|
.shadow_ignore_shaped = false,
|
|
|
|
.xinerama_shadow_crop = false,
|
2021-07-09 13:17:23 -04:00
|
|
|
.shadow_clip_list = NULL,
|
2019-05-27 17:59:56 -04:00
|
|
|
|
2020-09-30 11:39:47 -04:00
|
|
|
.corner_radius = 0,
|
|
|
|
|
2019-05-27 17:59:56 -04:00
|
|
|
.fade_in_step = 0.028,
|
|
|
|
.fade_out_step = 0.03,
|
|
|
|
.fade_delta = 10,
|
|
|
|
.no_fading_openclose = false,
|
|
|
|
.no_fading_destroyed_argb = false,
|
|
|
|
.fade_blacklist = NULL,
|
|
|
|
|
|
|
|
.inactive_opacity = 1.0,
|
|
|
|
.inactive_opacity_override = false,
|
|
|
|
.active_opacity = 1.0,
|
|
|
|
.frame_opacity = 1.0,
|
|
|
|
.detect_client_opacity = false,
|
|
|
|
|
2019-05-31 19:39:00 -04:00
|
|
|
.blur_method = BLUR_METHOD_NONE,
|
2020-03-21 13:00:19 -04:00
|
|
|
.blur_radius = 3,
|
2019-12-20 13:07:35 -05:00
|
|
|
.blur_deviation = 0.84089642,
|
2019-12-20 14:31:00 -05:00
|
|
|
.blur_strength = 5,
|
2019-05-27 17:59:56 -04:00
|
|
|
.blur_background_frame = false,
|
|
|
|
.blur_background_fixed = false,
|
|
|
|
.blur_background_blacklist = NULL,
|
2019-06-06 02:37:48 -04:00
|
|
|
.blur_kerns = NULL,
|
2019-06-07 16:53:23 -04:00
|
|
|
.blur_kernel_count = 0,
|
2022-07-17 12:49:35 -04:00
|
|
|
.window_shader_fg = NULL,
|
|
|
|
.window_shader_fg_rules = NULL,
|
2019-05-27 17:59:56 -04:00
|
|
|
.inactive_dim = 0.0,
|
|
|
|
.inactive_dim_fixed = false,
|
|
|
|
.invert_color_list = NULL,
|
|
|
|
.opacity_rules = NULL,
|
2019-11-06 19:19:20 -05:00
|
|
|
.max_brightness = 1.0,
|
2019-05-27 17:59:56 -04:00
|
|
|
|
|
|
|
.use_ewmh_active_win = false,
|
|
|
|
.focus_blacklist = NULL,
|
|
|
|
.detect_transient = false,
|
|
|
|
.detect_client_leader = false,
|
2019-06-07 19:13:28 -04:00
|
|
|
.no_ewmh_fullscreen = false,
|
2019-05-27 17:59:56 -04:00
|
|
|
|
|
|
|
.track_leader = false,
|
2020-09-05 10:46:31 -04:00
|
|
|
|
|
|
|
.rounded_corners_blacklist = NULL
|
2019-05-27 17:59:56 -04:00
|
|
|
};
|
2020-09-05 10:46:31 -04:00
|
|
|
// clang-format on
|
2019-05-27 17:59:56 -04:00
|
|
|
|
2019-03-10 08:34:37 -04:00
|
|
|
char *ret = NULL;
|
2018-12-24 13:28:00 -05:00
|
|
|
#ifdef CONFIG_LIBCONFIG
|
2019-03-10 08:34:37 -04:00
|
|
|
ret = parse_config_libconfig(opt, config_file, shadow_enable, fading_enable,
|
|
|
|
hasneg, winopt_mask);
|
2019-07-24 21:46:46 -04:00
|
|
|
#else
|
|
|
|
(void)config_file;
|
|
|
|
(void)shadow_enable;
|
|
|
|
(void)fading_enable;
|
|
|
|
(void)hasneg;
|
|
|
|
(void)winopt_mask;
|
2018-12-24 13:28:00 -05:00
|
|
|
#endif
|
2019-03-10 08:34:37 -04:00
|
|
|
return ret;
|
2018-12-04 11:28:19 -05:00
|
|
|
}
|