mirror of
https://github.com/DreamMaoMao/maomaowm.git
synced 2026-03-23 05:35:53 -04:00
Add opt-in session save and restore support
This commit is contained in:
parent
225fbda574
commit
cf197c5bf2
6 changed files with 887 additions and 0 deletions
|
|
@ -78,6 +78,17 @@ no_border_when_single=0
|
||||||
axis_bind_apply_timeout=100
|
axis_bind_apply_timeout=100
|
||||||
focus_on_activate=1
|
focus_on_activate=1
|
||||||
idleinhibit_ignore_visible=0
|
idleinhibit_ignore_visible=0
|
||||||
|
# Restore and save session state across compositor restarts.
|
||||||
|
# Disabled by default to preserve Mango's current behavior.
|
||||||
|
# Minimized restore is currently unsupported.
|
||||||
|
# Restore expects target outputs to already exist before clients map.
|
||||||
|
session_restore=0
|
||||||
|
# Relaunch mapping used when Mango cannot infer a launch command from a client.
|
||||||
|
# Format: session_launch=app_id|command
|
||||||
|
# or: session_launch=app_id|title|command
|
||||||
|
# session_launch=foot|foot
|
||||||
|
# session_launch=foot|gamma|foot -a foot -T gamma -e sh -lc "sleep 600"
|
||||||
|
# session_launch=org.kde.dolphin|dolphin .
|
||||||
sloppyfocus=1
|
sloppyfocus=1
|
||||||
warpcursor=1
|
warpcursor=1
|
||||||
focus_cross_monitor=0
|
focus_cross_monitor=0
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@ endif
|
||||||
executable('mango',
|
executable('mango',
|
||||||
'src/mango.c',
|
'src/mango.c',
|
||||||
'src/common/util.c',
|
'src/common/util.c',
|
||||||
|
'src/session/session.c',
|
||||||
'src/ext-protocol/wlr_ext_workspace_v1.c',
|
'src/ext-protocol/wlr_ext_workspace_v1.c',
|
||||||
wayland_sources,
|
wayland_sources,
|
||||||
dependencies : [
|
dependencies : [
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,12 @@ typedef struct {
|
||||||
char *value;
|
char *value;
|
||||||
} ConfigEnv;
|
} ConfigEnv;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char app_id[256];
|
||||||
|
char title[512];
|
||||||
|
char command[1024];
|
||||||
|
} ConfigSessionLaunchRule;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *id;
|
const char *id;
|
||||||
const char *title;
|
const char *title;
|
||||||
|
|
@ -251,6 +257,7 @@ typedef struct {
|
||||||
|
|
||||||
uint32_t axis_bind_apply_timeout;
|
uint32_t axis_bind_apply_timeout;
|
||||||
uint32_t focus_on_activate;
|
uint32_t focus_on_activate;
|
||||||
|
int32_t session_restore;
|
||||||
int32_t idleinhibit_ignore_visible;
|
int32_t idleinhibit_ignore_visible;
|
||||||
int32_t sloppyfocus;
|
int32_t sloppyfocus;
|
||||||
int32_t warpcursor;
|
int32_t warpcursor;
|
||||||
|
|
@ -352,6 +359,8 @@ typedef struct {
|
||||||
|
|
||||||
char **exec_once;
|
char **exec_once;
|
||||||
int32_t exec_once_count;
|
int32_t exec_once_count;
|
||||||
|
ConfigSessionLaunchRule *session_launch_rules;
|
||||||
|
int32_t session_launch_rules_count;
|
||||||
|
|
||||||
char *cursor_theme;
|
char *cursor_theme;
|
||||||
uint32_t cursor_size;
|
uint32_t cursor_size;
|
||||||
|
|
@ -1426,6 +1435,71 @@ bool parse_option(Config *config, char *key, char *value) {
|
||||||
config->allow_shortcuts_inhibit = atoi(value);
|
config->allow_shortcuts_inhibit = atoi(value);
|
||||||
} else if (strcmp(key, "allow_lock_transparent") == 0) {
|
} else if (strcmp(key, "allow_lock_transparent") == 0) {
|
||||||
config->allow_lock_transparent = atoi(value);
|
config->allow_lock_transparent = atoi(value);
|
||||||
|
} else if (strcmp(key, "session_restore") == 0) {
|
||||||
|
config->session_restore = atoi(value);
|
||||||
|
} else if (strcmp(key, "session_launch") == 0) {
|
||||||
|
ConfigSessionLaunchRule rule = {0};
|
||||||
|
ConfigSessionLaunchRule *new_rules = NULL;
|
||||||
|
char *first_sep = strchr(value, '|');
|
||||||
|
char *second_sep = NULL;
|
||||||
|
size_t app_len, title_len = 0, cmd_len;
|
||||||
|
|
||||||
|
if (!first_sep) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"\033[1m\033[31m[ERROR]:\033[33m Invalid session_launch "
|
||||||
|
"format. Expected app_id|command or "
|
||||||
|
"app_id|title|command\033[0m\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
second_sep = strchr(first_sep + 1, '|');
|
||||||
|
app_len = (size_t)(first_sep - value);
|
||||||
|
if (second_sep) {
|
||||||
|
title_len = (size_t)(second_sep - (first_sep + 1));
|
||||||
|
cmd_len = strlen(second_sep + 1);
|
||||||
|
} else {
|
||||||
|
cmd_len = strlen(first_sep + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app_len == 0 || cmd_len == 0 || app_len >= sizeof(rule.app_id) ||
|
||||||
|
cmd_len >= sizeof(rule.command) ||
|
||||||
|
title_len >= sizeof(rule.title)) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"\033[1m\033[31m[ERROR]:\033[33m Invalid session_launch "
|
||||||
|
"entry length\033[0m\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(rule.app_id, value, app_len);
|
||||||
|
rule.app_id[app_len] = '\0';
|
||||||
|
if (second_sep) {
|
||||||
|
memcpy(rule.title, first_sep + 1, title_len);
|
||||||
|
rule.title[title_len] = '\0';
|
||||||
|
memcpy(rule.command, second_sep + 1, cmd_len + 1);
|
||||||
|
} else {
|
||||||
|
memcpy(rule.command, first_sep + 1, cmd_len + 1);
|
||||||
|
}
|
||||||
|
trim_whitespace(rule.app_id);
|
||||||
|
trim_whitespace(rule.title);
|
||||||
|
trim_whitespace(rule.command);
|
||||||
|
if (rule.app_id[0] == '\0' || rule.command[0] == '\0') {
|
||||||
|
fprintf(stderr,
|
||||||
|
"\033[1m\033[31m[ERROR]:\033[33m session_launch requires "
|
||||||
|
"both app_id and command\033[0m\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_rules = realloc(config->session_launch_rules,
|
||||||
|
sizeof(*config->session_launch_rules) *
|
||||||
|
(config->session_launch_rules_count + 1));
|
||||||
|
if (!new_rules) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"\033[1m\033[31m[ERROR]:\033[33m Failed to allocate "
|
||||||
|
"session_launch rules\033[0m\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
config->session_launch_rules = new_rules;
|
||||||
|
config->session_launch_rules[config->session_launch_rules_count++] = rule;
|
||||||
} else if (strcmp(key, "no_border_when_single") == 0) {
|
} else if (strcmp(key, "no_border_when_single") == 0) {
|
||||||
config->no_border_when_single = atoi(value);
|
config->no_border_when_single = atoi(value);
|
||||||
} else if (strcmp(key, "no_radius_when_single") == 0) {
|
} else if (strcmp(key, "no_radius_when_single") == 0) {
|
||||||
|
|
@ -2826,6 +2900,14 @@ void free_circle_layout(Config *config) {
|
||||||
config->circle_layout_count = 0; // 重置计数
|
config->circle_layout_count = 0; // 重置计数
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void free_session_launch_rules(Config *config) {
|
||||||
|
if (config->session_launch_rules) {
|
||||||
|
free(config->session_launch_rules);
|
||||||
|
config->session_launch_rules = NULL;
|
||||||
|
}
|
||||||
|
config->session_launch_rules_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void free_baked_points(void) {
|
void free_baked_points(void) {
|
||||||
if (baked_points_move) {
|
if (baked_points_move) {
|
||||||
free(baked_points_move);
|
free(baked_points_move);
|
||||||
|
|
@ -3096,6 +3178,7 @@ void free_config(void) {
|
||||||
|
|
||||||
// 释放 circle_layout
|
// 释放 circle_layout
|
||||||
free_circle_layout(&config);
|
free_circle_layout(&config);
|
||||||
|
free_session_launch_rules(&config);
|
||||||
|
|
||||||
// 释放动画资源
|
// 释放动画资源
|
||||||
free_baked_points();
|
free_baked_points();
|
||||||
|
|
@ -3171,6 +3254,7 @@ void override_config(void) {
|
||||||
config.axis_bind_apply_timeout =
|
config.axis_bind_apply_timeout =
|
||||||
CLAMP_INT(config.axis_bind_apply_timeout, 0, 1000);
|
CLAMP_INT(config.axis_bind_apply_timeout, 0, 1000);
|
||||||
config.focus_on_activate = CLAMP_INT(config.focus_on_activate, 0, 1);
|
config.focus_on_activate = CLAMP_INT(config.focus_on_activate, 0, 1);
|
||||||
|
config.session_restore = CLAMP_INT(config.session_restore, 0, 1);
|
||||||
config.idleinhibit_ignore_visible =
|
config.idleinhibit_ignore_visible =
|
||||||
CLAMP_INT(config.idleinhibit_ignore_visible, 0, 1);
|
CLAMP_INT(config.idleinhibit_ignore_visible, 0, 1);
|
||||||
config.sloppyfocus = CLAMP_INT(config.sloppyfocus, 0, 1);
|
config.sloppyfocus = CLAMP_INT(config.sloppyfocus, 0, 1);
|
||||||
|
|
@ -3275,6 +3359,9 @@ void set_value_default() {
|
||||||
|
|
||||||
config.axis_bind_apply_timeout = 100;
|
config.axis_bind_apply_timeout = 100;
|
||||||
config.focus_on_activate = 1;
|
config.focus_on_activate = 1;
|
||||||
|
config.session_restore = 0;
|
||||||
|
config.session_launch_rules = NULL;
|
||||||
|
config.session_launch_rules_count = 0;
|
||||||
config.new_is_master = 1;
|
config.new_is_master = 1;
|
||||||
config.default_mfact = 0.55f;
|
config.default_mfact = 0.55f;
|
||||||
config.default_nmaster = 1;
|
config.default_nmaster = 1;
|
||||||
|
|
@ -3511,6 +3598,8 @@ bool parse_config(void) {
|
||||||
config.exec_count = 0;
|
config.exec_count = 0;
|
||||||
config.exec_once = NULL;
|
config.exec_once = NULL;
|
||||||
config.exec_once_count = 0;
|
config.exec_once_count = 0;
|
||||||
|
config.session_launch_rules = NULL;
|
||||||
|
config.session_launch_rules_count = 0;
|
||||||
config.scroller_proportion_preset = NULL;
|
config.scroller_proportion_preset = NULL;
|
||||||
config.scroller_proportion_preset_count = 0;
|
config.scroller_proportion_preset_count = 0;
|
||||||
config.circle_layout = NULL;
|
config.circle_layout = NULL;
|
||||||
|
|
|
||||||
258
src/mango.c
258
src/mango.c
|
|
@ -407,6 +407,7 @@ struct Client {
|
||||||
float focused_opacity;
|
float focused_opacity;
|
||||||
float unfocused_opacity;
|
float unfocused_opacity;
|
||||||
char oldmonname[128];
|
char oldmonname[128];
|
||||||
|
char session_launch_command[1024];
|
||||||
int32_t noblur;
|
int32_t noblur;
|
||||||
double master_mfact_per, master_inner_per, stack_inner_per;
|
double master_mfact_per, master_inner_per, stack_inner_per;
|
||||||
double old_master_mfact_per, old_master_inner_per, old_stack_inner_per;
|
double old_master_mfact_per, old_master_inner_per, old_stack_inner_per;
|
||||||
|
|
@ -941,6 +942,7 @@ struct Pertag {
|
||||||
};
|
};
|
||||||
|
|
||||||
#include "config/parse_config.h"
|
#include "config/parse_config.h"
|
||||||
|
#include "session/session.h"
|
||||||
|
|
||||||
static struct wl_signal mango_print_status;
|
static struct wl_signal mango_print_status;
|
||||||
|
|
||||||
|
|
@ -1011,6 +1013,257 @@ static struct wl_event_source *sync_keymap;
|
||||||
#include "layout/horizontal.h"
|
#include "layout/horizontal.h"
|
||||||
#include "layout/vertical.h"
|
#include "layout/vertical.h"
|
||||||
|
|
||||||
|
static void session_write_json_string(FILE *out, const char *str) {
|
||||||
|
const unsigned char *p = (const unsigned char *)(str ? str : "");
|
||||||
|
|
||||||
|
fputc('"', out);
|
||||||
|
for (; *p != '\0'; ++p) {
|
||||||
|
switch (*p) {
|
||||||
|
case '\\':
|
||||||
|
fputs("\\\\", out);
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
fputs("\\\"", out);
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
fputs("\\n", out);
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
fputs("\\r", out);
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
fputs("\\t", out);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (*p < 0x20)
|
||||||
|
fprintf(out, "\\u%04x", *p);
|
||||||
|
else
|
||||||
|
fputc(*p, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fputc('"', out);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *mango_session_client_appid(Client *c) { return client_get_appid(c); }
|
||||||
|
|
||||||
|
const char *mango_session_client_title(Client *c) { return client_get_title(c); }
|
||||||
|
|
||||||
|
const char *mango_session_client_monitor(Client *c) {
|
||||||
|
return (c && c->mon && c->mon->wlr_output) ? c->mon->wlr_output->name : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *mango_session_lookup_launch_command(const char *app_id,
|
||||||
|
const char *title) {
|
||||||
|
if (!app_id || app_id[0] == '\0')
|
||||||
|
return "";
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < config.session_launch_rules_count; ++i) {
|
||||||
|
ConfigSessionLaunchRule *rule = &config.session_launch_rules[i];
|
||||||
|
if (strcmp(rule->app_id, app_id) != 0)
|
||||||
|
continue;
|
||||||
|
if (rule->title[0] == '\0')
|
||||||
|
continue;
|
||||||
|
if (title && strcmp(rule->title, title) == 0)
|
||||||
|
return rule->command;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < config.session_launch_rules_count; ++i) {
|
||||||
|
ConfigSessionLaunchRule *rule = &config.session_launch_rules[i];
|
||||||
|
if (strcmp(rule->app_id, app_id) == 0 && rule->title[0] == '\0')
|
||||||
|
return rule->command;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void mango_session_remember_client_launch_command(Client *c, const char *command) {
|
||||||
|
if (!c)
|
||||||
|
return;
|
||||||
|
|
||||||
|
memset(c->session_launch_command, 0, sizeof(c->session_launch_command));
|
||||||
|
if (!command || command[0] == '\0')
|
||||||
|
return;
|
||||||
|
|
||||||
|
strncpy(c->session_launch_command, command,
|
||||||
|
sizeof(c->session_launch_command) - 1);
|
||||||
|
c->session_launch_command[sizeof(c->session_launch_command) - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
void mango_session_spawn_command(const char *command) {
|
||||||
|
if (!command || command[0] == '\0')
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (fork() == 0) {
|
||||||
|
signal(SIGSEGV, SIG_IGN);
|
||||||
|
signal(SIGABRT, SIG_IGN);
|
||||||
|
signal(SIGILL, SIG_IGN);
|
||||||
|
|
||||||
|
dup2(STDERR_FILENO, STDOUT_FILENO);
|
||||||
|
setsid();
|
||||||
|
|
||||||
|
execlp("sh", "sh", "-c", command, (char *)NULL);
|
||||||
|
execlp("bash", "bash", "-c", command, (char *)NULL);
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool session_client_should_save(Client *c) {
|
||||||
|
const char *appid;
|
||||||
|
|
||||||
|
if (!c || c->iskilling || !c->mon || c->tags == 0)
|
||||||
|
return false;
|
||||||
|
if (client_is_unmanaged(c) || client_is_x11_popup(c))
|
||||||
|
return false;
|
||||||
|
if (client_get_parent(c) != NULL)
|
||||||
|
return false;
|
||||||
|
if (c->is_in_scratchpad || c->is_scratchpad_show || c->isnamedscratchpad)
|
||||||
|
return false;
|
||||||
|
if (c->swallowing || c->swallowedby)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
appid = client_get_appid(c);
|
||||||
|
return appid && appid[0] != '\0' && strcmp(appid, "broken") != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t mango_session_is_config_enabled(void) { return config.session_restore; }
|
||||||
|
|
||||||
|
static const char *session_client_launch_command(Client *c) {
|
||||||
|
const char *appid;
|
||||||
|
const char *title;
|
||||||
|
const char *mapped_command;
|
||||||
|
|
||||||
|
if (c && c->session_launch_command[0] != '\0')
|
||||||
|
return c->session_launch_command;
|
||||||
|
|
||||||
|
appid = client_get_appid(c);
|
||||||
|
|
||||||
|
if (!appid || appid[0] == '\0' || strcmp(appid, "broken") == 0)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
title = client_get_title(c);
|
||||||
|
mapped_command = mango_session_lookup_launch_command(appid, title);
|
||||||
|
if (mapped_command && mapped_command[0] != '\0')
|
||||||
|
return mapped_command;
|
||||||
|
|
||||||
|
/* First relaunch pass: use app_id as the best-effort launch command.
|
||||||
|
* Explicit config mappings will override this for wrappers and PWAs. */
|
||||||
|
return appid;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t mango_session_write_snapshot(FILE *out) {
|
||||||
|
Client *c;
|
||||||
|
int32_t count = 0;
|
||||||
|
|
||||||
|
fputs("[\n", out);
|
||||||
|
wl_list_for_each(c, &clients, link) {
|
||||||
|
const char *monitor_name;
|
||||||
|
const char *appid;
|
||||||
|
const char *launch_command;
|
||||||
|
const char *title;
|
||||||
|
const char *separator;
|
||||||
|
|
||||||
|
if (!session_client_should_save(c))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
monitor_name =
|
||||||
|
(c->mon && c->mon->wlr_output) ? c->mon->wlr_output->name : "";
|
||||||
|
appid = client_get_appid(c);
|
||||||
|
launch_command = session_client_launch_command(c);
|
||||||
|
title = client_get_title(c);
|
||||||
|
separator = count == 0 ? "" : ",\n";
|
||||||
|
|
||||||
|
fputs(separator, out);
|
||||||
|
fputs(" {\n", out);
|
||||||
|
fputs(" \"app_id\": ", out);
|
||||||
|
session_write_json_string(out, appid);
|
||||||
|
fputs(",\n \"title\": ", out);
|
||||||
|
session_write_json_string(out, title);
|
||||||
|
fprintf(out,
|
||||||
|
",\n \"pid\": %d,\n \"monitor\": ",
|
||||||
|
client_get_pid(c));
|
||||||
|
session_write_json_string(out, monitor_name);
|
||||||
|
fputs(",\n \"launch_command\": ", out);
|
||||||
|
session_write_json_string(out, launch_command);
|
||||||
|
fprintf(out,
|
||||||
|
",\n \"tags\": %u,\n \"is_floating\": %d,\n "
|
||||||
|
"\"is_fullscreen\": %d,\n \"is_minimized\": %d,\n "
|
||||||
|
"\"geom\": {\"x\": %d, \"y\": %d, \"width\": %d, \"height\": %d},"
|
||||||
|
"\n \"float_geom\": {\"x\": %d, \"y\": %d, \"width\": %d, "
|
||||||
|
"\"height\": %d}\n }",
|
||||||
|
c->tags, c->isfloating, c->isfullscreen, c->isminimized, c->geom.x,
|
||||||
|
c->geom.y, c->geom.width, c->geom.height, c->float_geom.x,
|
||||||
|
c->float_geom.y, c->float_geom.width, c->float_geom.height);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
fputs("\n]\n", out);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mango_session_apply_restore_entry(Client *c,
|
||||||
|
const SessionRestoreEntry *entry) {
|
||||||
|
Monitor *target = NULL, *m = NULL;
|
||||||
|
uint32_t tags;
|
||||||
|
|
||||||
|
if (!c || !entry || !c->mon)
|
||||||
|
return;
|
||||||
|
|
||||||
|
tags = entry->tags;
|
||||||
|
if (tags == 0)
|
||||||
|
tags = c->tags ? c->tags : c->mon->tagset[c->mon->seltags];
|
||||||
|
|
||||||
|
if (entry->monitor[0] != '\0') {
|
||||||
|
wl_list_for_each(m, &mons, link) {
|
||||||
|
if (!m->wlr_output->enabled)
|
||||||
|
continue;
|
||||||
|
if (strcmp(m->wlr_output->name, entry->monitor) == 0) {
|
||||||
|
target = m;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target && target != c->mon) {
|
||||||
|
setmon(c, target, tags, false);
|
||||||
|
} else {
|
||||||
|
c->tags = tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
arrange(c->mon, false, false);
|
||||||
|
|
||||||
|
if (entry->is_floating) {
|
||||||
|
c->float_geom = (struct wlr_box){
|
||||||
|
.x = entry->float_geom.x,
|
||||||
|
.y = entry->float_geom.y,
|
||||||
|
.width = entry->float_geom.width,
|
||||||
|
.height = entry->float_geom.height,
|
||||||
|
};
|
||||||
|
if (c->float_geom.width <= 0 || c->float_geom.height <= 0) {
|
||||||
|
c->float_geom = (struct wlr_box){
|
||||||
|
.x = entry->geom.x,
|
||||||
|
.y = entry->geom.y,
|
||||||
|
.width = entry->geom.width,
|
||||||
|
.height = entry->geom.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
c->geom = c->float_geom;
|
||||||
|
setfloating(c, 1);
|
||||||
|
resize(c, c->geom, 0);
|
||||||
|
} else if (c->isfloating) {
|
||||||
|
setfloating(c, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry->is_fullscreen && !c->isfullscreen) {
|
||||||
|
setfullscreen(c, 1);
|
||||||
|
} else if (!entry->is_fullscreen && c->isfullscreen) {
|
||||||
|
setfullscreen(c, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip minimized restore for now to avoid introducing new focus changes. */
|
||||||
|
client_update_oldmonname_record(c, c->mon);
|
||||||
|
printstatus();
|
||||||
|
}
|
||||||
|
|
||||||
void client_change_mon(Client *c, Monitor *m) {
|
void client_change_mon(Client *c, Monitor *m) {
|
||||||
setmon(c, m, c->tags, true);
|
setmon(c, m, c->tags, true);
|
||||||
if (c->isfloating) {
|
if (c->isfloating) {
|
||||||
|
|
@ -2264,6 +2517,8 @@ void cleanuplisteners(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanup(void) {
|
void cleanup(void) {
|
||||||
|
session_save_now(true);
|
||||||
|
session_shutdown();
|
||||||
cleanuplisteners();
|
cleanuplisteners();
|
||||||
#ifdef XWAYLAND
|
#ifdef XWAYLAND
|
||||||
wlr_xwayland_destroy(xwayland);
|
wlr_xwayland_destroy(xwayland);
|
||||||
|
|
@ -4208,6 +4463,7 @@ mapnotify(struct wl_listener *listener, void *data) {
|
||||||
// make sure the animation is open type
|
// make sure the animation is open type
|
||||||
c->is_pending_open_animation = true;
|
c->is_pending_open_animation = true;
|
||||||
resize(c, c->geom, 0);
|
resize(c, c->geom, 0);
|
||||||
|
session_handle_client_mapped(c);
|
||||||
printstatus();
|
printstatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4978,6 +5234,7 @@ run(char *startup_cmd) {
|
||||||
|
|
||||||
run_exec();
|
run_exec();
|
||||||
run_exec_once();
|
run_exec_once();
|
||||||
|
session_maybe_restore_startup();
|
||||||
|
|
||||||
/* Run the Wayland event loop. This does not return until you exit the
|
/* Run the Wayland event loop. This does not return until you exit the
|
||||||
* compositor. Starting the backend rigged up all of the necessary event
|
* compositor. Starting the backend rigged up all of the necessary event
|
||||||
|
|
@ -5494,6 +5751,7 @@ void setup(void) {
|
||||||
setenv("_JAVA_AWT_WM_NONREPARENTING", "1", 1);
|
setenv("_JAVA_AWT_WM_NONREPARENTING", "1", 1);
|
||||||
|
|
||||||
parse_config();
|
parse_config();
|
||||||
|
session_init();
|
||||||
if (cli_debug_log) {
|
if (cli_debug_log) {
|
||||||
config.log_level = WLR_DEBUG;
|
config.log_level = WLR_DEBUG;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
489
src/session/session.c
Normal file
489
src/session/session.c
Normal file
|
|
@ -0,0 +1,489 @@
|
||||||
|
#include "session.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../common/util.h"
|
||||||
|
|
||||||
|
extern int32_t mango_session_is_config_enabled(void);
|
||||||
|
extern int32_t mango_session_write_snapshot(FILE *out);
|
||||||
|
extern const char *mango_session_client_appid(Client *c);
|
||||||
|
extern const char *mango_session_client_title(Client *c);
|
||||||
|
extern const char *mango_session_client_monitor(Client *c);
|
||||||
|
extern const char *mango_session_lookup_launch_command(const char *app_id,
|
||||||
|
const char *title);
|
||||||
|
extern void mango_session_remember_client_launch_command(Client *c,
|
||||||
|
const char *command);
|
||||||
|
extern void mango_session_spawn_command(const char *command);
|
||||||
|
extern void mango_session_apply_restore_entry(Client *c,
|
||||||
|
const SessionRestoreEntry *entry);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
SessionRestoreEntry entry;
|
||||||
|
bool used;
|
||||||
|
} PendingSessionEntry;
|
||||||
|
|
||||||
|
static PendingSessionEntry *pending_entries;
|
||||||
|
static size_t pending_count;
|
||||||
|
static bool restore_started;
|
||||||
|
|
||||||
|
static bool mkdir_p(const char *dir) {
|
||||||
|
char tmp[PATH_MAX];
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
if (!dir || dir[0] == '\0')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
len = strlen(dir);
|
||||||
|
if (len >= sizeof(tmp))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
memcpy(tmp, dir, len + 1);
|
||||||
|
|
||||||
|
for (char *p = tmp + 1; *p != '\0'; ++p) {
|
||||||
|
if (*p != '/')
|
||||||
|
continue;
|
||||||
|
*p = '\0';
|
||||||
|
if (mkdir(tmp, 0755) < 0 && errno != EEXIST)
|
||||||
|
return false;
|
||||||
|
*p = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
return mkdir(tmp, 0755) == 0 || errno == EEXIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *session_data_dir(void) {
|
||||||
|
const char *xdg_data = getenv("XDG_DATA_HOME");
|
||||||
|
if (xdg_data && xdg_data[0] != '\0')
|
||||||
|
return string_printf("%s/mango", xdg_data);
|
||||||
|
|
||||||
|
const char *home = getenv("HOME");
|
||||||
|
if (!home || home[0] == '\0')
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return string_printf("%s/.local/share/mango", home);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *session_file_path(void) {
|
||||||
|
char *dir = session_data_dir();
|
||||||
|
char *path;
|
||||||
|
if (!dir)
|
||||||
|
return NULL;
|
||||||
|
path = string_printf("%s/session.json", dir);
|
||||||
|
free(dir);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_pending_entries(void) {
|
||||||
|
free(pending_entries);
|
||||||
|
pending_entries = NULL;
|
||||||
|
pending_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *skip_ws(const char *p) {
|
||||||
|
while (p && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t'))
|
||||||
|
++p;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *find_matching_brace(const char *start) {
|
||||||
|
int depth = 0;
|
||||||
|
bool in_string = false;
|
||||||
|
bool escaped = false;
|
||||||
|
|
||||||
|
for (const char *p = start; p && *p != '\0'; ++p) {
|
||||||
|
if (in_string) {
|
||||||
|
if (escaped) {
|
||||||
|
escaped = false;
|
||||||
|
} else if (*p == '\\') {
|
||||||
|
escaped = true;
|
||||||
|
} else if (*p == '"') {
|
||||||
|
in_string = false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*p == '"') {
|
||||||
|
in_string = true;
|
||||||
|
} else if (*p == '{') {
|
||||||
|
depth++;
|
||||||
|
} else if (*p == '}') {
|
||||||
|
depth--;
|
||||||
|
if (depth == 0)
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parse_json_string_value(const char *value, char *dest, size_t dest_size) {
|
||||||
|
size_t i = 0;
|
||||||
|
const char *p = skip_ws(value);
|
||||||
|
|
||||||
|
if (!p || *p != '"' || dest_size == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
++p;
|
||||||
|
while (*p != '\0' && *p != '"' && i + 1 < dest_size) {
|
||||||
|
if (*p == '\\') {
|
||||||
|
++p;
|
||||||
|
if (*p == '\0')
|
||||||
|
break;
|
||||||
|
switch (*p) {
|
||||||
|
case 'n':
|
||||||
|
dest[i++] = '\n';
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
dest[i++] = '\r';
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
dest[i++] = '\t';
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
case '"':
|
||||||
|
case '/':
|
||||||
|
dest[i++] = *p;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dest[i++] = *p;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++p;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
dest[i++] = *p++;
|
||||||
|
}
|
||||||
|
|
||||||
|
dest[i] = '\0';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool extract_json_string(const char *obj, const char *key, char *dest,
|
||||||
|
size_t dest_size) {
|
||||||
|
const char *p = strstr(obj, key);
|
||||||
|
if (!p)
|
||||||
|
return false;
|
||||||
|
p = strchr(p, ':');
|
||||||
|
if (!p)
|
||||||
|
return false;
|
||||||
|
return parse_json_string_value(p + 1, dest, dest_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool extract_json_int(const char *obj, const char *key, int32_t *out) {
|
||||||
|
char *end = NULL;
|
||||||
|
const char *p = strstr(obj, key);
|
||||||
|
long value;
|
||||||
|
|
||||||
|
if (!p)
|
||||||
|
return false;
|
||||||
|
p = strchr(p, ':');
|
||||||
|
if (!p)
|
||||||
|
return false;
|
||||||
|
p = skip_ws(p + 1);
|
||||||
|
if (!p)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
value = strtol(p, &end, 10);
|
||||||
|
if (end == p)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*out = (int32_t)value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool extract_json_object_range(const char *obj, const char *key,
|
||||||
|
const char **start, const char **end) {
|
||||||
|
const char *p = strstr(obj, key);
|
||||||
|
if (!p)
|
||||||
|
return false;
|
||||||
|
p = strchr(p, ':');
|
||||||
|
if (!p)
|
||||||
|
return false;
|
||||||
|
p = skip_ws(p + 1);
|
||||||
|
if (!p || *p != '{')
|
||||||
|
return false;
|
||||||
|
*start = p;
|
||||||
|
*end = find_matching_brace(p);
|
||||||
|
return *end != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parse_geom_object(const char *obj, const char *key, SessionRect *geom) {
|
||||||
|
const char *start = NULL, *end = NULL;
|
||||||
|
char *sub = NULL;
|
||||||
|
bool ok;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
if (!extract_json_object_range(obj, key, &start, &end))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
len = (size_t)(end - start + 1);
|
||||||
|
sub = ecalloc(len + 1, 1);
|
||||||
|
memcpy(sub, start, len);
|
||||||
|
|
||||||
|
ok = extract_json_int(sub, "\"x\"", &geom->x) &&
|
||||||
|
extract_json_int(sub, "\"y\"", &geom->y) &&
|
||||||
|
extract_json_int(sub, "\"width\"", &geom->width) &&
|
||||||
|
extract_json_int(sub, "\"height\"", &geom->height);
|
||||||
|
|
||||||
|
free(sub);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parse_session_entry(const char *obj, SessionRestoreEntry *entry) {
|
||||||
|
int32_t tags = 0;
|
||||||
|
|
||||||
|
memset(entry, 0, sizeof(*entry));
|
||||||
|
|
||||||
|
if (!extract_json_string(obj, "\"app_id\"", entry->app_id,
|
||||||
|
sizeof(entry->app_id)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
extract_json_string(obj, "\"title\"", entry->title, sizeof(entry->title));
|
||||||
|
extract_json_string(obj, "\"monitor\"", entry->monitor,
|
||||||
|
sizeof(entry->monitor));
|
||||||
|
extract_json_string(obj, "\"launch_command\"", entry->launch_command,
|
||||||
|
sizeof(entry->launch_command));
|
||||||
|
extract_json_int(obj, "\"pid\"", &entry->pid);
|
||||||
|
extract_json_int(obj, "\"tags\"", &tags);
|
||||||
|
entry->tags = (uint32_t)tags;
|
||||||
|
extract_json_int(obj, "\"is_floating\"", &entry->is_floating);
|
||||||
|
extract_json_int(obj, "\"is_fullscreen\"", &entry->is_fullscreen);
|
||||||
|
extract_json_int(obj, "\"is_minimized\"", &entry->is_minimized);
|
||||||
|
parse_geom_object(obj, "\"geom\"", &entry->geom);
|
||||||
|
parse_geom_object(obj, "\"float_geom\"", &entry->float_geom);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool load_pending_entries(void) {
|
||||||
|
char *path = session_file_path();
|
||||||
|
char *contents = NULL;
|
||||||
|
FILE *in = NULL;
|
||||||
|
const char *cursor;
|
||||||
|
bool loaded = false;
|
||||||
|
|
||||||
|
free_pending_entries();
|
||||||
|
if (!path)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
in = fopen(path, "r");
|
||||||
|
if (!in)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
if (fseek(in, 0, SEEK_END) != 0)
|
||||||
|
goto cleanup;
|
||||||
|
long size = ftell(in);
|
||||||
|
if (size < 0 || fseek(in, 0, SEEK_SET) != 0)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
contents = ecalloc((size_t)size + 1, 1);
|
||||||
|
if (fread(contents, 1, (size_t)size, in) != (size_t)size)
|
||||||
|
goto cleanup;
|
||||||
|
contents[size] = '\0';
|
||||||
|
|
||||||
|
cursor = contents;
|
||||||
|
while ((cursor = strchr(cursor, '{')) != NULL) {
|
||||||
|
const char *end = find_matching_brace(cursor);
|
||||||
|
SessionRestoreEntry entry;
|
||||||
|
char *obj;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
if (!end)
|
||||||
|
break;
|
||||||
|
|
||||||
|
len = (size_t)(end - cursor + 1);
|
||||||
|
obj = ecalloc(len + 1, 1);
|
||||||
|
memcpy(obj, cursor, len);
|
||||||
|
|
||||||
|
if (parse_session_entry(obj, &entry)) {
|
||||||
|
PendingSessionEntry *new_entries = realloc(
|
||||||
|
pending_entries, sizeof(*pending_entries) * (pending_count + 1));
|
||||||
|
if (!new_entries) {
|
||||||
|
free(obj);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
pending_entries = new_entries;
|
||||||
|
pending_entries[pending_count].entry = entry;
|
||||||
|
pending_entries[pending_count].used = false;
|
||||||
|
pending_count++;
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(obj);
|
||||||
|
cursor = end + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (in)
|
||||||
|
fclose(in);
|
||||||
|
free(contents);
|
||||||
|
free(path);
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PendingSessionEntry *find_pending_match(const char *appid, const char *title,
|
||||||
|
const char *monitor) {
|
||||||
|
PendingSessionEntry *fallback = NULL;
|
||||||
|
PendingSessionEntry *monitor_match = NULL;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < pending_count; ++i) {
|
||||||
|
PendingSessionEntry *candidate = &pending_entries[i];
|
||||||
|
if (candidate->used)
|
||||||
|
continue;
|
||||||
|
if (strcmp(candidate->entry.app_id, appid) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (title && title[0] != '\0' &&
|
||||||
|
strcmp(candidate->entry.title, title) == 0) {
|
||||||
|
if (monitor && monitor[0] != '\0' &&
|
||||||
|
strcmp(candidate->entry.monitor, monitor) == 0)
|
||||||
|
return candidate;
|
||||||
|
if (!fallback)
|
||||||
|
fallback = candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!monitor_match && monitor && monitor[0] != '\0' &&
|
||||||
|
strcmp(candidate->entry.monitor, monitor) == 0) {
|
||||||
|
monitor_match = candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fallback)
|
||||||
|
fallback = candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return monitor_match ? monitor_match : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool session_resolve_launch_command(SessionRestoreEntry *entry) {
|
||||||
|
const char *mapped_command;
|
||||||
|
|
||||||
|
if (!entry || entry->app_id[0] == '\0')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Explicit user mapping wins over best-effort persisted launch data. */
|
||||||
|
mapped_command = mango_session_lookup_launch_command(entry->app_id,
|
||||||
|
entry->title);
|
||||||
|
if (mapped_command && mapped_command[0] != '\0') {
|
||||||
|
strncpy(entry->launch_command, mapped_command,
|
||||||
|
sizeof(entry->launch_command) - 1);
|
||||||
|
entry->launch_command[sizeof(entry->launch_command) - 1] = '\0';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry->launch_command[0] != '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void session_spawn_restore_entries(void) {
|
||||||
|
for (size_t i = 0; i < pending_count; ++i) {
|
||||||
|
SessionRestoreEntry *entry = &pending_entries[i].entry;
|
||||||
|
|
||||||
|
if (!session_resolve_launch_command(entry))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
mango_session_spawn_command(entry->launch_command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void session_init(void) {}
|
||||||
|
|
||||||
|
void session_shutdown(void) {
|
||||||
|
free_pending_entries();
|
||||||
|
restore_started = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void session_maybe_restore_startup(void) {
|
||||||
|
if (!session_is_enabled() || restore_started)
|
||||||
|
return;
|
||||||
|
|
||||||
|
restore_started = true;
|
||||||
|
if (!load_pending_entries())
|
||||||
|
return;
|
||||||
|
|
||||||
|
session_spawn_restore_entries();
|
||||||
|
}
|
||||||
|
|
||||||
|
void session_handle_client_mapped(Client *c) {
|
||||||
|
PendingSessionEntry *match;
|
||||||
|
const char *appid;
|
||||||
|
const char *title;
|
||||||
|
const char *monitor;
|
||||||
|
|
||||||
|
if (!restore_started || pending_count == 0 || !c)
|
||||||
|
return;
|
||||||
|
|
||||||
|
appid = mango_session_client_appid(c);
|
||||||
|
if (!appid || appid[0] == '\0' || strcmp(appid, "broken") == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
title = mango_session_client_title(c);
|
||||||
|
monitor = mango_session_client_monitor(c);
|
||||||
|
match = find_pending_match(appid, title, monitor);
|
||||||
|
if (!match)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mango_session_remember_client_launch_command(c, match->entry.launch_command);
|
||||||
|
mango_session_apply_restore_entry(c, &match->entry);
|
||||||
|
match->used = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void session_handle_client_destroyed(Client *c) {
|
||||||
|
(void)c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void session_save_now(bool is_final_shutdown) {
|
||||||
|
FILE *out = NULL;
|
||||||
|
char *dir = NULL, *tmp_path = NULL, *final_path = NULL;
|
||||||
|
int32_t count = 0;
|
||||||
|
|
||||||
|
if (!session_is_enabled())
|
||||||
|
return;
|
||||||
|
|
||||||
|
dir = session_data_dir();
|
||||||
|
if (!dir || !mkdir_p(dir))
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
tmp_path = string_printf("%s/session.json.tmp", dir);
|
||||||
|
final_path = string_printf("%s/session.json", dir);
|
||||||
|
if (!tmp_path || !final_path)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
out = fopen(tmp_path, "w");
|
||||||
|
if (!out)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
count = mango_session_write_snapshot(out);
|
||||||
|
if (fclose(out) != 0) {
|
||||||
|
out = NULL;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
out = NULL;
|
||||||
|
|
||||||
|
if (count <= 0 && is_final_shutdown) {
|
||||||
|
unlink(tmp_path);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rename(tmp_path, final_path) != 0)
|
||||||
|
unlink(tmp_path);
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (out)
|
||||||
|
fclose(out);
|
||||||
|
free(dir);
|
||||||
|
free(tmp_path);
|
||||||
|
free(final_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool session_is_enabled(void) { return mango_session_is_config_enabled() != 0; }
|
||||||
|
|
||||||
|
bool session_is_restorable_client(Client *c) {
|
||||||
|
(void)c;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
39
src/session/session.h
Normal file
39
src/session/session.h
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#ifndef SESSION_H
|
||||||
|
#define SESSION_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef struct Client Client;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t x;
|
||||||
|
int32_t y;
|
||||||
|
int32_t width;
|
||||||
|
int32_t height;
|
||||||
|
} SessionRect;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char app_id[256];
|
||||||
|
char title[512];
|
||||||
|
char monitor[128];
|
||||||
|
char launch_command[1024];
|
||||||
|
int32_t pid;
|
||||||
|
uint32_t tags;
|
||||||
|
int32_t is_floating;
|
||||||
|
int32_t is_fullscreen;
|
||||||
|
int32_t is_minimized;
|
||||||
|
SessionRect geom;
|
||||||
|
SessionRect float_geom;
|
||||||
|
} SessionRestoreEntry;
|
||||||
|
|
||||||
|
void session_init(void);
|
||||||
|
void session_shutdown(void);
|
||||||
|
void session_maybe_restore_startup(void);
|
||||||
|
void session_handle_client_mapped(Client *c);
|
||||||
|
void session_handle_client_destroyed(Client *c);
|
||||||
|
void session_save_now(bool is_final_shutdown);
|
||||||
|
bool session_is_enabled(void);
|
||||||
|
bool session_is_restorable_client(Client *c);
|
||||||
|
|
||||||
|
#endif
|
||||||
Loading…
Add table
Add a link
Reference in a new issue