1
0
mirror of https://github.com/artizirk/wdisplays.git synced 2024-11-22 00:40:59 +02:00
This commit is contained in:
TanShaochang 2024-05-31 20:57:25 +02:00 committed by GitHub
commit 1db3702294
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 299 additions and 2 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/build/ /build/
.vscode
*.user *.user

View File

@ -9,6 +9,7 @@ Compositors that are known to support the protocol are [Sway] and [Wayfire].
The goal of this project is to allow precise adjustment of display settings in The goal of this project is to allow precise adjustment of display settings in
kiosks, digital signage, and other elaborate multi-monitor setups. kiosks, digital signage, and other elaborate multi-monitor setups.
![Screenshot](wdisplays.png) ![Screenshot](wdisplays.png)
# Installation # Installation
@ -70,8 +71,16 @@ It's intended to be the Wayland equivalent of an xrandr GUI, like [ARandR].
Sway, like i3, doesn't save any settings unless you put them in the config Sway, like i3, doesn't save any settings unless you put them in the config
file. See man `sway-output`. If you want to have multiple configurations file. See man `sway-output`. If you want to have multiple configurations
depending on the monitors connected, you'll need to use an external program depending on the monitors connected, you'll need to use an external program
like [kanshi] or [way-displays]. Integration with that and other external like [kanshi] or [way-displays].
daemons is planned.
When you apply a new change, the setting will be defaultly added to $HOME/.config/kanshi/config,
if there is already a profile for the same monitors combination, the change will be applied on
existing one.
you can add kanshi autostart to your sway config:
```
exec kanshi
exec_always kanshictl reload
```
### How do I add support to my compositor? ### How do I add support to my compositor?

View File

@ -20,6 +20,7 @@ executable(
'outputs.c', 'outputs.c',
'overlay.c', 'overlay.c',
'render.c', 'render.c',
'store.c',
resources, resources,
], ],
dependencies : [ dependencies : [

View File

@ -28,6 +28,8 @@
#include "wlr-screencopy-unstable-v1-client-protocol.h" #include "wlr-screencopy-unstable-v1-client-protocol.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h"
extern int store_config(struct wl_list *outputs);
static void noop() { static void noop() {
// This space is intentionally left blank // This space is intentionally left blank
} }
@ -52,6 +54,11 @@ static void config_handle_succeeded(void *data,
struct wd_pending_config *pending = data; struct wd_pending_config *pending = data;
zwlr_output_configuration_v1_destroy(config); zwlr_output_configuration_v1_destroy(config);
wd_ui_apply_done(pending->state, pending->outputs); wd_ui_apply_done(pending->state, pending->outputs);
if (store_config(pending->outputs) == 0)
{
wd_ui_show_error(pending->state,
"Change was applied successfully and config was saved.");
}
destroy_pending(pending); destroy_pending(pending);
} }

279
src/store.c Normal file
View File

@ -0,0 +1,279 @@
#include "wdisplays.h"
#include <ctype.h>
#include <regex.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wayland-client-protocol.h>
#include <limits.h>
#include <stdbool.h>
#define MAX_NAME_LENGTH 256
#define MAX_MONITORS_NUM 10
struct wd_head_config;
struct profile_line {
int start;
int end;
};
char *get_config_file_path() {
char defaultPath[PATH_MAX]; // platform based marco PATH_MAX
char wdisplaysPath[PATH_MAX];
// if $XDG_CONFIG_HOME is set, use it
{
const char *configDir = getenv("XDG_CONFIG_HOME");
char defaultConfigDir[PATH_MAX];
if (configDir == NULL) {
const char *homeDir = getenv("HOME");
if (homeDir == NULL) {
perror("Cannot find home directory");
return NULL;
}
snprintf(defaultConfigDir, sizeof(defaultConfigDir), "%s/.config", homeDir);
} else {
snprintf(defaultConfigDir, sizeof(defaultConfigDir), "%s", configDir);
}
snprintf(defaultPath, sizeof(defaultPath), "%s/kanshi/config", defaultConfigDir);
snprintf(wdisplaysPath, sizeof(wdisplaysPath), "%s/wdisplays/config", defaultConfigDir);
}
FILE *wdisplaysFile = fopen(wdisplaysPath, "r");
if (wdisplaysFile != NULL) {
char line[LINE_MAX]; // LINE_MAX is a platform based marco
// try to match "store_path" term
while (fgets(line, sizeof(line), wdisplaysFile) != NULL) {
if (strstr(line, "store_path") != NULL) {
// if found, extract path
char *pathStart = strchr(line, '=');
if (pathStart != NULL) {
pathStart++; // skip '='
char *pathEnd = strchr(pathStart, '\n');
if (pathEnd != NULL) {
*pathEnd = '\0'; // replace '\n' with '\0'
fclose(wdisplaysFile);
return strdup(pathStart); // return path
}
}
}
}
fclose(wdisplaysFile);
}
// if store_path is not found in wdisplays config file, return default path
return strdup(defaultPath);
}
struct profile_line match(char **descriptions, int num, char *filename) {
struct profile_line matched_profile;
matched_profile.start = -1;
matched_profile.end = -1;
// -1 means not found
FILE *configFile = fopen(filename, "r");
if (configFile == NULL) {
perror("File open failed.");
return matched_profile;
}
// buffer to store each line
char buffer[LINE_MAX];
char profileName[MAX_NAME_LENGTH];
int profileStartLine = 0; // mark the start line of matched profile
int profileEndLine = 0; // mark the end line of matched profile
int lineCount = 0; // current line number
while (fgets(buffer, sizeof(buffer), configFile) != NULL) {
lineCount++;
// check if "profile" keyword is in the line
if (strstr(buffer, "profile") != NULL) {
// extract profile name
sscanf(buffer, "profile %s {", profileName);
// the number of matched outputs
uint32_t profileMatchedNum = 0;
// record the start line of the profile
profileStartLine = lineCount;
while (fgets(buffer, sizeof(buffer), configFile) != NULL) {
lineCount++;
// check if the profile ends
if (buffer[0] == '}') {
profileEndLine = lineCount;
break;
}
char outputName[MAX_NAME_LENGTH];
// 从当前行提取输出名称
char *trimmedBuffer = buffer;
while (isspace(*trimmedBuffer)) {
trimmedBuffer++; // skip leading spaces
}
sscanf(trimmedBuffer, "output \"%99[^\"]\"", outputName); // extract output name
// check if the output name is in the descriptions
bool matched = false;
for (int i = 0; descriptions[i] != NULL; i++) {
if (strcmp(outputName, descriptions[i]) == 0) {
matched = true;
profileMatchedNum++;
break;
}
}
if (!matched) {
// if any output is not matched, break
profileMatchedNum = 0;
break;
}
}
if (profileMatchedNum == num) {
printf("Matched profile:%s\n", profileName);
printf("Start line:%d\n", profileStartLine);
matched_profile.start = profileStartLine;
printf("End line:%d\n", profileEndLine);
matched_profile.end = profileEndLine;
fclose(configFile);
return matched_profile;
}
}
}
fclose(configFile);
printf("Cannot find existing profile to match\n");
return matched_profile;
}
int store_config(struct wl_list *outputs) {
char *file_name = get_config_file_path();
char tmp_file_name[PATH_MAX];
sprintf(tmp_file_name,"%s.tmp",file_name);
char *descriptions[MAX_MONITORS_NUM];
for (int i = 0; i < MAX_MONITORS_NUM; i++) {
descriptions[i] = NULL;
}
char *outputConfigs[MAX_MONITORS_NUM];
for (int i = 0; i < MAX_MONITORS_NUM; i++) {
outputConfigs[i] = (char *)malloc(MAX_NAME_LENGTH);
}
struct wd_head_config *output;
int description_index = 0;
wl_list_for_each(output, outputs, link) {
struct wd_head *head = output->head;
// for transform
char *trans_str = (char *)malloc(15 * sizeof(char));
switch (output->transform) {
case WL_OUTPUT_TRANSFORM_NORMAL:
strcpy(trans_str, "normal");
break;
case WL_OUTPUT_TRANSFORM_90:
strcpy(trans_str, "90");
break;
case WL_OUTPUT_TRANSFORM_180:
strcpy(trans_str, "180");
break;
case WL_OUTPUT_TRANSFORM_270:
strcpy(trans_str, "270");
break;
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
strcpy(trans_str, "flipped-90");
break;
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
strcpy(trans_str, "flipped-180");
break;
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
strcpy(trans_str, "flipped-270");
break;
default:
strcpy(trans_str, "normal");
break;
}
if (description_index < MAX_MONITORS_NUM) {
descriptions[description_index] = strdup(head->description);
// write output config in given format
sprintf(
outputConfigs[description_index],
"output \"%s\" position %d,%d mode %dx%d@%.4f scale %.2f transform %s",
head->description, output->x, output->y, output->width,
output->height, output->refresh / 1.0e3, output->scale, trans_str);
description_index++;
} else {
free(trans_str);
printf("Too many monitor! 10 is the");
return 1;
}
free(trans_str);
}
int num_of_monitors = description_index;
struct profile_line matched_profile;
matched_profile = match(descriptions, num_of_monitors, file_name);
if (matched_profile.start == -1) {
// append new profile
FILE *file = fopen(file_name, "a");
if (file == NULL) {
perror("File open failed.");
free(file_name);
return 1;
}
fprintf(file, "\nprofile {\n");
for (int i = 0; i<num_of_monitors; i++) {
fprintf(file, " %s\n", outputConfigs[i]);
free(outputConfigs[i]);
}
fprintf(file, "}");
fclose(file);
} else if (matched_profile.start < matched_profile.end) {
// rewrite correspondence lines
FILE *file = fopen(file_name, "r");
if (file == NULL) {
perror("File open failed.");
free(file_name);
return 1;
}
FILE *tmp = fopen(tmp_file_name, "w");
if (tmp == NULL) {
perror("Tmp file cannot be created.");
fclose(file);
free(file_name);
return 1;
}
char _buffer[LINE_MAX];
int _line = 0;
int _i_output = 0;
while (fgets(_buffer, sizeof(_buffer), file) != NULL) {
if (_line >= matched_profile.start && _line < matched_profile.end - 1) {
if(_i_output>=num_of_monitors){
perror("Null pointer");
fclose(tmp);
fclose(file);
return 1;
}
fprintf(tmp," %s\n",outputConfigs[_i_output]);
free(outputConfigs[_i_output]);
_i_output++;
} else{
fprintf(tmp,"%s",_buffer);
}
_line++;
}
fclose(file);
fclose(tmp);
remove(file_name);
rename(tmp_file_name, file_name);
free(file_name);
}
return 0;
}