Skip to content

Commit

Permalink
Free tab layouts on window close
Browse files Browse the repository at this point in the history
Signed-off-by: Artem Senichev <[email protected]>
  • Loading branch information
artemsen committed Oct 21, 2023
1 parent fd20319 commit 72bc11c
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 78 deletions.
48 changes: 28 additions & 20 deletions src/layouts.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,42 @@

#include <stdlib.h>

/** State descriptor: window key and its layout. */
/** State descriptor: window/tab and its layout. */
struct state {
unsigned long key;
int layout;
uint32_t window;
uint32_t tab;
int layout;
};
static struct state* states;
static int states_sz;

/**
* Find state description for specified window key.
* @param[in] window window Id
* @param[in] window window id
* @param[in] tab subwindow (tab) id
* @return pointer to the state descriptor or NULL if not found
*/
static struct state* find_state(unsigned long key)
static struct state* find_state(uint32_t window, uint32_t tab)
{
for (int i = 0; i < states_sz; ++i) {
struct state* entry = &states[i];
if (entry->key == key) {
if (entry->window == window && entry->tab == tab) {
return entry;
}
}
return NULL;
}

int get_layout(unsigned long key)
int get_layout(uint32_t window, uint32_t tab)
{
const struct state* entry = find_state(key);
const struct state* entry = find_state(window, tab);
return entry ? entry->layout : INVALID_LAYOUT;
}

void put_layout(unsigned long key, int layout)
void put_layout(uint32_t window, uint32_t tab, int layout)
{
// search for existing descriptor
struct state* entry = find_state(key);
struct state* entry = find_state(window, tab);
if (entry) {
entry->layout = layout;
return;
Expand All @@ -47,8 +49,9 @@ void put_layout(unsigned long key, int layout)
// search for free descriptor
for (int i = 0; i < states_sz; ++i) {
struct state* entry = &states[i];
if (entry->key == INVALID_KEY) {
entry->key = key;
if (entry->window == 0 && entry->tab == 0) {
entry->window = window;
entry->tab = tab;
entry->layout = layout;
return;
}
Expand All @@ -61,20 +64,25 @@ void put_layout(unsigned long key, int layout)
for (int i = old_sz; i < states_sz; ++i) {
struct state* entry = &states[i];
if (i == old_sz) {
entry->key = key;
entry->window = window;
entry->tab = tab;
entry->layout = layout;
} else {
entry->key = INVALID_KEY;
entry->window = 0;
entry->tab = 0;
entry->layout = INVALID_LAYOUT;
}
}
}

void rm_layout(unsigned long key)
void rm_layout(uint32_t window)
{
struct state* entry = find_state(key);
if (entry) {
// mark descriptor as free cell
entry->key = INVALID_KEY;
entry->layout = INVALID_LAYOUT;
for (int i = 0; i < states_sz; ++i) {
struct state* entry = &states[i];
if (entry->window == window) {
entry->window = 0;
entry->tab = 0;
entry->layout = INVALID_LAYOUT;
}
}
}
17 changes: 10 additions & 7 deletions src/layouts.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,28 @@

#pragma once

#define INVALID_KEY 0
#include <stdint.h>

#define INVALID_LAYOUT -1

/**
* Get layout information for specified window.
* @param[in] key window key
* @param[in] window window id
* @param[in] tab subwindow (tab) id
* @return layout index, INVALID_LAYOUT if not found
*/
int get_layout(unsigned long key);
int get_layout(uint32_t window, uint32_t tab);

/**
* Put layout information into storage.
* @param[in] key window key
* @param[in] window window id
* @param[in] tab subwindow (tab) id
* @param[in] layout keyboard layout index
*/
void put_layout(unsigned long key, int layout);
void put_layout(uint32_t window, uint32_t tab, int layout);

/**
* Remove layout information from storage.
* @param[in] key window key
* @param[in] window window id
*/
void rm_layout(unsigned long key);
void rm_layout(uint32_t window);
77 changes: 34 additions & 43 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,120 +29,115 @@

/** Static context. */
struct context {
unsigned long last_key; ///< Key of the last focused window
int last_wnd; ///< Identifier of the last focused window
uint32_t last_wnd; ///< Identifier of the last focused window
uint32_t last_tab; ///< Identifier of the last focused tab
int default_layout; ///< Default layout for new windows
int current_layout; ///< Current layout index
int switch_timeout; ///< Ignored time between layout change and focus lost
size_t switch_timeout; ///< Ignored time between layout change and focus lost
struct timespec switch_timestamp; ///< Timestamp of the last layout change
char** tabapps; ///< List of tab-enbled applications
size_t tabapps_num; ///< Size of tabbaps list
};
static struct context ctx = {
.last_key = INVALID_KEY,
.last_wnd = -1,
.last_wnd = 0,
.last_tab = 0,
.default_layout = DEFAULT_LAYOUT,
.current_layout = INVALID_LAYOUT,
.switch_timeout = DEFAULT_TIMEOUT,
};

/* Generate unique window key. */
static unsigned long window_key(int wnd_id, const char* app_id, const char* title)
/**
* Generate unique tab id for tab-enabled applications.
* @param[in] app_id application id
* @param[in] title title of the window
* @return tab id or 0 if application does not support tabs
*/
static uint32_t get_tab_id(const char* app_id, const char* title)
{
unsigned long key = wnd_id;

// check if the current container belongs to the web browser, we will
// use window title (which is a tab name) to generate unique id
for (size_t i = 0; i < ctx.tabapps_num; ++i) {
if (strcmp(app_id, ctx.tabapps[i]) == 0) {
// djb2 hash
key = 5381;
uint32_t hash = 5381;
while (*title) {
key = ((key << 5) + key) + *title++;
hash = ((hash << 5) + hash) + *title++;
}
key += wnd_id;
break;
return hash;
}
}

return key;
return 0;
}

/** Focus change handler. */
static int on_focus_change(int wnd_id, const char* app_id, const char* title)
{
int layout;
const unsigned long key = window_key(wnd_id, app_id, title);
const uint32_t tab_id = get_tab_id(app_id, title);

// save current layout for previously focused window
if (ctx.last_key != INVALID_KEY &&
ctx.current_layout != INVALID_LAYOUT) {
if (ctx.last_wnd && ctx.current_layout != INVALID_LAYOUT) {
if (ctx.switch_timeout == 0) {
layout = ctx.current_layout;
} else {
// check for timeout
unsigned long long elapsed;
size_t elapsed;
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
elapsed = TIMESPEC_MS(ts) - TIMESPEC_MS(ctx.switch_timestamp);
if (elapsed > (unsigned long long)ctx.switch_timeout) {
if (elapsed > ctx.switch_timeout) {
layout = ctx.current_layout;
} else {
layout = INVALID_LAYOUT;
}
}
if (layout != INVALID_LAYOUT) {
TRACE("store layout=%d, window=%d:0x%lx",
layout, ctx.last_wnd, ctx.last_key);
put_layout(ctx.last_key, layout);
TRACE("store layout=%d, window=%x:%x", layout, ctx.last_wnd, ctx.last_tab);
put_layout(ctx.last_wnd, ctx.last_tab, layout);
}
}

// define layout for currently focused window
layout = get_layout(key);
TRACE("found layout=%d, window=%d:0x%lx", layout, wnd_id, key);
layout = get_layout(wnd_id, tab_id);
TRACE("found layout=%d, window=%x:%x", layout, wnd_id, tab_id);
if (layout == INVALID_LAYOUT && ctx.default_layout != INVALID_LAYOUT) {
layout = ctx.default_layout; // set default
}
if (layout == ctx.current_layout) {
layout = INVALID_LAYOUT; // already set
}

ctx.last_key = key;
ctx.last_wnd = wnd_id;
ctx.last_tab = tab_id;

TRACE("set layout=%d, window=%d:0x%lx", layout, wnd_id, key);
TRACE("set layout=%d, window=%x:%x", layout, wnd_id, tab_id);
return layout;
}

/** Title change handler. */
static int on_title_change(int wnd_id, const char* app_id, const char* title)
{
if (ctx.last_wnd == wnd_id) {
if (ctx.last_wnd == (uint32_t)wnd_id) {
TRACE("window_id=%d", wnd_id);
return on_focus_change(wnd_id, app_id, title);
}
return INVALID_LAYOUT;
}

/** Window close handler. */
static void on_window_close(int wnd_id, const char* app_id, const char* title)
static void on_window_close(int wnd_id)
{
const unsigned long key = window_key(wnd_id, app_id, title);
TRACE("window=%x:*", wnd_id);
rm_layout(wnd_id);

TRACE("window=%d:0x%lx", wnd_id, key);
rm_layout(key);

if (key == ctx.last_key) {
// reset last window key to prevent saving layout for the closed window
ctx.last_key = INVALID_KEY;
if (ctx.last_wnd == (uint32_t)wnd_id) {
// reset last window id to prevent saving layout for the closed window
ctx.last_wnd = 0;
}
}

/** Keyboard layout change handler. */
static void on_layout_change(int layout)
{
TRACE("layout=%d, window=%d:0x%lx", layout, ctx.last_wnd, ctx.last_key);
TRACE("layout=%d, window=%x:%x", layout, ctx.last_wnd, ctx.last_tab);
ctx.current_layout = layout;
clock_gettime(CLOCK_MONOTONIC, &ctx.switch_timestamp);
}
Expand Down Expand Up @@ -207,10 +202,6 @@ int main(int argc, char* argv[])
break;
case 't':
ctx.switch_timeout = atoi(optarg);
if (ctx.switch_timeout < 0) {
fprintf(stderr, "Invalid timeout value: %s\n", optarg);
return EXIT_FAILURE;
}
break;
case 'a':
set_tabapps(optarg);
Expand Down
10 changes: 5 additions & 5 deletions src/sway.c
Original file line number Diff line number Diff line change
Expand Up @@ -227,13 +227,13 @@ static void container_info(struct json_object* msg, int* wnd_id,
if (!json_object_object_get_ex(msg, "container", &cnt_node)) {
return;
}
if (json_object_object_get_ex(cnt_node, "id", &sub_node)) {
if (wnd_id && json_object_object_get_ex(cnt_node, "id", &sub_node)) {
*wnd_id = json_object_get_int(sub_node);
}
if (json_object_object_get_ex(cnt_node, "app_id", &sub_node)) {
if (app_id && json_object_object_get_ex(cnt_node, "app_id", &sub_node)) {
*app_id = json_object_get_string(sub_node);
}
if (json_object_object_get_ex(cnt_node, "name", &sub_node)) {
if (title && json_object_object_get_ex(cnt_node, "name", &sub_node)) {
*title = json_object_get_string(sub_node);
}
}
Expand Down Expand Up @@ -299,8 +299,8 @@ int sway_monitor(on_focus fn_focus, on_title fn_title,
ipc_change_layout(sock, layout);
}
} else if (strcmp(event_name, "close") == 0) {
container_info(msg, &wnd_id, &app_id, &title);
fn_close(wnd_id, app_id, title);
container_info(msg, &wnd_id, NULL, NULL);
fn_close(wnd_id);
} else if (strcmp(event_name, "xkb_layout") == 0) {
const int layout = layout_index(msg);
if (layout >= 0) {
Expand Down
4 changes: 1 addition & 3 deletions src/sway.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,8 @@ typedef int (*on_title)(int wnd_id, const char* app_id, const char* title);
/**
* Callback function: Window close handler.
* @param[in] wnd_id identifier of currently focused window (container)
* @param[in] app_id application id
* @param[in] title title of the window
*/
typedef void (*on_close)(int wnd_id, const char* app_id, const char* title);
typedef void (*on_close)(int wnd_id);

/**
* Callback function: Keyboard layout change handler.
Expand Down

0 comments on commit 72bc11c

Please sign in to comment.