feat: add directional window resizing
feat: support multi-battery monitoring fix: prevent windows from exceeding layout bounds refactor: adjust snap constraints for borders and title bars
This commit is contained in:
parent
f4af2b1f1e
commit
ea12677dac
BIN
build/layout.o
BIN
build/layout.o
Binary file not shown.
BIN
build/main.o
BIN
build/main.o
Binary file not shown.
BIN
build/panel.o
BIN
build/panel.o
Binary file not shown.
BIN
build/systray.o
BIN
build/systray.o
Binary file not shown.
@ -25,6 +25,11 @@
|
||||
#define MAX_NOTIFICATIONS 32
|
||||
#define MAX_KEYBINDINGS 64
|
||||
|
||||
#define RESIZE_LEFT 1
|
||||
#define RESIZE_RIGHT 2
|
||||
#define RESIZE_TOP 4
|
||||
#define RESIZE_BOTTOM 8
|
||||
|
||||
#define DEFAULT_BORDER_WIDTH 0
|
||||
#define DEFAULT_TITLE_HEIGHT 28
|
||||
#define DEFAULT_PANEL_HEIGHT 32
|
||||
@ -168,6 +173,7 @@ typedef struct {
|
||||
int drag_orig_x, drag_orig_y;
|
||||
int drag_orig_w, drag_orig_h;
|
||||
bool resizing;
|
||||
int drag_direction;
|
||||
|
||||
Client *pending_focus_client;
|
||||
long pending_focus_time;
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#define MAX_TRAY_ICONS 32
|
||||
#define MAX_BATTERIES 4
|
||||
#define TRAY_ICON_SIZE 22
|
||||
#define TRAY_ICON_SPACING 4
|
||||
|
||||
@ -59,6 +60,22 @@ typedef struct {
|
||||
int time_remaining;
|
||||
} BatteryState;
|
||||
|
||||
typedef struct {
|
||||
char name[32];
|
||||
bool present;
|
||||
bool charging;
|
||||
int percentage;
|
||||
unsigned long energy_now;
|
||||
unsigned long energy_full;
|
||||
} SingleBattery;
|
||||
|
||||
typedef struct {
|
||||
int count;
|
||||
SingleBattery batteries[MAX_BATTERIES];
|
||||
bool any_charging;
|
||||
int combined_percentage;
|
||||
} MultiBatteryState;
|
||||
|
||||
typedef struct {
|
||||
Window window;
|
||||
int x, y;
|
||||
@ -70,6 +87,7 @@ typedef struct {
|
||||
extern WifiState wifi_state;
|
||||
extern AudioState audio_state;
|
||||
extern BatteryState battery_state;
|
||||
extern MultiBatteryState multi_battery_state;
|
||||
extern VolumeSlider *volume_slider;
|
||||
|
||||
void systray_init(void);
|
||||
@ -106,6 +124,7 @@ void systray_lock(void);
|
||||
void systray_unlock(void);
|
||||
|
||||
BatteryState systray_get_battery_snapshot(void);
|
||||
MultiBatteryState systray_get_multi_battery_snapshot(void);
|
||||
AudioState systray_get_audio_snapshot(void);
|
||||
|
||||
void xembed_init(void);
|
||||
|
||||
39
src/layout.c
39
src/layout.c
@ -84,6 +84,7 @@ void layout_arrange_tiling(int workspace)
|
||||
}
|
||||
|
||||
int stack_width = area_width - master_width - 3 * gap;
|
||||
if (stack_width < 50) stack_width = 50;
|
||||
|
||||
int i = 0;
|
||||
int master_y = area_y + gap;
|
||||
@ -120,6 +121,16 @@ void layout_arrange_tiling(int workspace)
|
||||
stack_y += h + gap;
|
||||
}
|
||||
|
||||
int max_y = area_y + area_height - gap;
|
||||
int max_x = area_x + area_width - gap;
|
||||
|
||||
if (y + h > max_y) {
|
||||
h = max_y - y;
|
||||
}
|
||||
if (x + w > max_x) {
|
||||
w = max_x - x;
|
||||
}
|
||||
|
||||
int actual_h = h - title_height - 2 * border;
|
||||
int actual_w = w - 2 * border;
|
||||
|
||||
@ -268,21 +279,25 @@ void layout_apply_snap_constraint(Client *c, int area_x, int area_y,
|
||||
return;
|
||||
}
|
||||
|
||||
int title_h = (dwn->config && dwn->config->show_decorations) ?
|
||||
config_get_title_height() : 0;
|
||||
int border = c->border_width;
|
||||
|
||||
int half_w = area_w / 2;
|
||||
int half_h = area_h / 2;
|
||||
|
||||
switch (c->snap.horizontal) {
|
||||
case SNAP_H_LEFT:
|
||||
c->x = area_x + gap;
|
||||
c->width = half_w - gap * 2;
|
||||
c->x = area_x + gap + border;
|
||||
c->width = half_w - gap * 2 - 2 * border;
|
||||
break;
|
||||
case SNAP_H_RIGHT:
|
||||
c->x = area_x + half_w + gap;
|
||||
c->width = half_w - gap * 2;
|
||||
c->x = area_x + half_w + gap + border;
|
||||
c->width = half_w - gap * 2 - 2 * border;
|
||||
break;
|
||||
case SNAP_H_FULL:
|
||||
c->x = area_x + gap;
|
||||
c->width = area_w - gap * 2;
|
||||
c->x = area_x + gap + border;
|
||||
c->width = area_w - gap * 2 - 2 * border;
|
||||
break;
|
||||
case SNAP_H_NONE:
|
||||
break;
|
||||
@ -290,16 +305,16 @@ void layout_apply_snap_constraint(Client *c, int area_x, int area_y,
|
||||
|
||||
switch (c->snap.vertical) {
|
||||
case SNAP_V_TOP:
|
||||
c->y = area_y + gap;
|
||||
c->height = half_h - gap * 2;
|
||||
c->y = area_y + gap + title_h + border;
|
||||
c->height = half_h - gap * 2 - title_h - 2 * border;
|
||||
break;
|
||||
case SNAP_V_BOTTOM:
|
||||
c->y = area_y + half_h + gap;
|
||||
c->height = half_h - gap * 2;
|
||||
c->y = area_y + half_h + gap + title_h + border;
|
||||
c->height = half_h - gap * 2 - title_h - 2 * border;
|
||||
break;
|
||||
case SNAP_V_FULL:
|
||||
c->y = area_y + gap;
|
||||
c->height = area_h - gap * 2;
|
||||
c->y = area_y + gap + title_h + border;
|
||||
c->height = area_h - gap * 2 - title_h - 2 * border;
|
||||
break;
|
||||
case SNAP_V_NONE:
|
||||
break;
|
||||
|
||||
37
src/main.c
37
src/main.c
@ -467,6 +467,7 @@ static void handle_button_press(XButtonEvent *ev)
|
||||
dwn->drag_orig_x = c->x;
|
||||
dwn->drag_orig_y = c->y;
|
||||
dwn->resizing = false;
|
||||
dwn->drag_direction = 0;
|
||||
|
||||
api_emit_drag_started(c->window, false);
|
||||
|
||||
@ -488,6 +489,7 @@ static void handle_button_press(XButtonEvent *ev)
|
||||
dwn->drag_orig_w = c->width;
|
||||
dwn->drag_orig_h = c->height;
|
||||
dwn->resizing = true;
|
||||
dwn->drag_direction = direction;
|
||||
|
||||
api_emit_drag_started(c->window, true);
|
||||
|
||||
@ -545,11 +547,36 @@ static void handle_motion_notify(XMotionEvent *ev)
|
||||
int dy = ev->y_root - dwn->drag_start_y;
|
||||
|
||||
if (dwn->resizing) {
|
||||
int new_w = dwn->drag_orig_w + dx;
|
||||
int new_h = dwn->drag_orig_h + dy;
|
||||
if (new_w < 50) new_w = 50;
|
||||
if (new_h < 50) new_h = 50;
|
||||
client_resize(c, new_w, new_h);
|
||||
int dir = dwn->drag_direction;
|
||||
int new_x = dwn->drag_orig_x;
|
||||
int new_y = dwn->drag_orig_y;
|
||||
int new_w = dwn->drag_orig_w;
|
||||
int new_h = dwn->drag_orig_h;
|
||||
|
||||
if (dir & RESIZE_LEFT) {
|
||||
new_x = dwn->drag_orig_x + dx;
|
||||
new_w = dwn->drag_orig_w - dx;
|
||||
} else if (dir & RESIZE_RIGHT) {
|
||||
new_w = dwn->drag_orig_w + dx;
|
||||
}
|
||||
|
||||
if (dir & RESIZE_TOP) {
|
||||
new_y = dwn->drag_orig_y + dy;
|
||||
new_h = dwn->drag_orig_h - dy;
|
||||
} else if (dir & RESIZE_BOTTOM) {
|
||||
new_h = dwn->drag_orig_h + dy;
|
||||
}
|
||||
|
||||
if (new_w < 50) {
|
||||
if (dir & RESIZE_LEFT) new_x = dwn->drag_orig_x + dwn->drag_orig_w - 50;
|
||||
new_w = 50;
|
||||
}
|
||||
if (new_h < 50) {
|
||||
if (dir & RESIZE_TOP) new_y = dwn->drag_orig_y + dwn->drag_orig_h - 50;
|
||||
new_h = 50;
|
||||
}
|
||||
|
||||
client_move_resize(c, new_x, new_y, new_w, new_h);
|
||||
} else {
|
||||
client_move(c, dwn->drag_orig_x + dx, dwn->drag_orig_y + dy);
|
||||
}
|
||||
|
||||
48
src/panel.c
48
src/panel.c
@ -790,10 +790,23 @@ static int panel_calculate_stats_width(void)
|
||||
sys_stats.load_1min, sys_stats.load_5min, sys_stats.load_15min);
|
||||
total += panel_text_width(buf, len) + WIDGET_SPACING;
|
||||
|
||||
BatteryState bat_snap = systray_get_battery_snapshot();
|
||||
if (bat_snap.present) {
|
||||
const char *bat_icon = bat_snap.charging ? "[+]" : "[=]";
|
||||
len = snprintf(buf, sizeof(buf), "%s%d%%", bat_icon, bat_snap.percentage);
|
||||
MultiBatteryState multi_bat = systray_get_multi_battery_snapshot();
|
||||
if (multi_bat.count > 0) {
|
||||
const char *bat_icon = multi_bat.any_charging ? "[+]" : "[=]";
|
||||
if (multi_bat.count == 1) {
|
||||
len = snprintf(buf, sizeof(buf), "%s%d%%", bat_icon, multi_bat.combined_percentage);
|
||||
} else {
|
||||
int offset = snprintf(buf, sizeof(buf), "%s%d%% (", bat_icon, multi_bat.combined_percentage);
|
||||
for (int i = 0; i < multi_bat.count && offset < (int)sizeof(buf) - 10; i++) {
|
||||
if (i > 0) {
|
||||
offset += snprintf(buf + offset, sizeof(buf) - offset, " ");
|
||||
}
|
||||
offset += snprintf(buf + offset, sizeof(buf) - offset, "B%d:%d%%",
|
||||
i + 1, multi_bat.batteries[i].percentage);
|
||||
}
|
||||
snprintf(buf + offset, sizeof(buf) - offset, ")");
|
||||
len = strlen(buf);
|
||||
}
|
||||
total += panel_text_width(buf, len) + WIDGET_SPACING;
|
||||
}
|
||||
|
||||
@ -859,19 +872,32 @@ static void panel_render_system_stats(Panel *panel, int x, int *width)
|
||||
panel_draw_text(panel->buffer, current_x, text_y, stats_buf, len, load_color);
|
||||
current_x += panel_text_width(stats_buf, len) + WIDGET_SPACING;
|
||||
|
||||
BatteryState bat_snap = systray_get_battery_snapshot();
|
||||
if (bat_snap.present) {
|
||||
MultiBatteryState multi_bat = systray_get_multi_battery_snapshot();
|
||||
if (multi_bat.count > 0) {
|
||||
unsigned long bat_color = colors->panel_fg;
|
||||
if (bat_snap.percentage <= 20 && !bat_snap.charging) {
|
||||
if (multi_bat.combined_percentage <= 20 && !multi_bat.any_charging) {
|
||||
bat_color = colors->workspace_urgent;
|
||||
} else if (bat_snap.percentage <= 40 && !bat_snap.charging) {
|
||||
} else if (multi_bat.combined_percentage <= 40 && !multi_bat.any_charging) {
|
||||
bat_color = 0xFFA500;
|
||||
} else if (bat_snap.charging) {
|
||||
} else if (multi_bat.any_charging) {
|
||||
bat_color = colors->workspace_active;
|
||||
}
|
||||
|
||||
const char *bat_icon = bat_snap.charging ? "[+]" : "[=]";
|
||||
len = snprintf(stats_buf, sizeof(stats_buf), "%s%d%%", bat_icon, bat_snap.percentage);
|
||||
const char *bat_icon = multi_bat.any_charging ? "[+]" : "[=]";
|
||||
if (multi_bat.count == 1) {
|
||||
len = snprintf(stats_buf, sizeof(stats_buf), "%s%d%%", bat_icon, multi_bat.combined_percentage);
|
||||
} else {
|
||||
int offset = snprintf(stats_buf, sizeof(stats_buf), "%s%d%% (", bat_icon, multi_bat.combined_percentage);
|
||||
for (int i = 0; i < multi_bat.count && offset < (int)sizeof(stats_buf) - 10; i++) {
|
||||
if (i > 0) {
|
||||
offset += snprintf(stats_buf + offset, sizeof(stats_buf) - offset, " ");
|
||||
}
|
||||
offset += snprintf(stats_buf + offset, sizeof(stats_buf) - offset, "B%d:%d%%",
|
||||
i + 1, multi_bat.batteries[i].percentage);
|
||||
}
|
||||
snprintf(stats_buf + offset, sizeof(stats_buf) - offset, ")");
|
||||
len = strlen(stats_buf);
|
||||
}
|
||||
panel_draw_text(panel->buffer, current_x, text_y, stats_buf, len, bat_color);
|
||||
current_x += panel_text_width(stats_buf, len) + WIDGET_SPACING;
|
||||
}
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
WifiState wifi_state = {0};
|
||||
AudioState audio_state = {0};
|
||||
BatteryState battery_state = {0};
|
||||
MultiBatteryState multi_battery_state = {0};
|
||||
VolumeSlider *volume_slider = NULL;
|
||||
|
||||
TrayIcon tray_icons[MAX_TRAY_ICONS];
|
||||
@ -183,6 +184,7 @@ void battery_update_state(void)
|
||||
char value[64];
|
||||
FILE *fp;
|
||||
|
||||
memset(&multi_battery_state, 0, sizeof(multi_battery_state));
|
||||
battery_state.present = false;
|
||||
battery_state.charging = false;
|
||||
battery_state.percentage = 0;
|
||||
@ -192,24 +194,75 @@ void battery_update_state(void)
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long total_energy_now = 0;
|
||||
unsigned long total_energy_full = 0;
|
||||
bool any_charging = false;
|
||||
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (entry->d_name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (multi_battery_state.count >= MAX_BATTERIES) {
|
||||
break;
|
||||
}
|
||||
|
||||
snprintf(path, sizeof(path), "/sys/class/power_supply/%s/type", entry->d_name);
|
||||
fp = fopen(path, "r");
|
||||
if (fp != NULL) {
|
||||
if (fgets(value, sizeof(value), fp) != NULL) {
|
||||
value[strcspn(value, "\n")] = '\0';
|
||||
if (strcmp(value, "Battery") == 0) {
|
||||
battery_state.present = true;
|
||||
SingleBattery *bat = &multi_battery_state.batteries[multi_battery_state.count];
|
||||
bat->present = true;
|
||||
size_t name_len = strlen(entry->d_name);
|
||||
if (name_len >= sizeof(bat->name)) {
|
||||
name_len = sizeof(bat->name) - 1;
|
||||
}
|
||||
memcpy(bat->name, entry->d_name, name_len);
|
||||
bat->name[name_len] = '\0';
|
||||
|
||||
snprintf(path, sizeof(path), "/sys/class/power_supply/%s/energy_now", entry->d_name);
|
||||
FILE *energy_fp = fopen(path, "r");
|
||||
if (energy_fp != NULL) {
|
||||
if (fgets(value, sizeof(value), energy_fp) != NULL) {
|
||||
bat->energy_now = strtoul(value, NULL, 10);
|
||||
}
|
||||
fclose(energy_fp);
|
||||
} else {
|
||||
snprintf(path, sizeof(path), "/sys/class/power_supply/%s/charge_now", entry->d_name);
|
||||
energy_fp = fopen(path, "r");
|
||||
if (energy_fp != NULL) {
|
||||
if (fgets(value, sizeof(value), energy_fp) != NULL) {
|
||||
bat->energy_now = strtoul(value, NULL, 10);
|
||||
}
|
||||
fclose(energy_fp);
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(path, sizeof(path), "/sys/class/power_supply/%s/energy_full", entry->d_name);
|
||||
energy_fp = fopen(path, "r");
|
||||
if (energy_fp != NULL) {
|
||||
if (fgets(value, sizeof(value), energy_fp) != NULL) {
|
||||
bat->energy_full = strtoul(value, NULL, 10);
|
||||
}
|
||||
fclose(energy_fp);
|
||||
} else {
|
||||
snprintf(path, sizeof(path), "/sys/class/power_supply/%s/charge_full", entry->d_name);
|
||||
energy_fp = fopen(path, "r");
|
||||
if (energy_fp != NULL) {
|
||||
if (fgets(value, sizeof(value), energy_fp) != NULL) {
|
||||
bat->energy_full = strtoul(value, NULL, 10);
|
||||
}
|
||||
fclose(energy_fp);
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(path, sizeof(path), "/sys/class/power_supply/%s/capacity", entry->d_name);
|
||||
FILE *cap_fp = fopen(path, "r");
|
||||
if (cap_fp != NULL) {
|
||||
if (fgets(value, sizeof(value), cap_fp) != NULL) {
|
||||
battery_state.percentage = atoi(value);
|
||||
bat->percentage = atoi(value);
|
||||
}
|
||||
fclose(cap_fp);
|
||||
}
|
||||
@ -219,20 +272,42 @@ void battery_update_state(void)
|
||||
if (status_fp != NULL) {
|
||||
if (fgets(value, sizeof(value), status_fp) != NULL) {
|
||||
value[strcspn(value, "\n")] = '\0';
|
||||
battery_state.charging = (strcmp(value, "Charging") == 0 ||
|
||||
strcmp(value, "Full") == 0);
|
||||
bat->charging = (strcmp(value, "Charging") == 0 ||
|
||||
strcmp(value, "Full") == 0);
|
||||
if (bat->charging) {
|
||||
any_charging = true;
|
||||
}
|
||||
}
|
||||
fclose(status_fp);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
break;
|
||||
total_energy_now += bat->energy_now;
|
||||
total_energy_full += bat->energy_full;
|
||||
multi_battery_state.count++;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
multi_battery_state.any_charging = any_charging;
|
||||
|
||||
if (total_energy_full > 0) {
|
||||
multi_battery_state.combined_percentage = (int)((total_energy_now * 100) / total_energy_full);
|
||||
} else if (multi_battery_state.count > 0) {
|
||||
int total_pct = 0;
|
||||
for (int i = 0; i < multi_battery_state.count; i++) {
|
||||
total_pct += multi_battery_state.batteries[i].percentage;
|
||||
}
|
||||
multi_battery_state.combined_percentage = total_pct / multi_battery_state.count;
|
||||
}
|
||||
|
||||
if (multi_battery_state.count > 0) {
|
||||
battery_state.present = true;
|
||||
battery_state.charging = any_charging;
|
||||
battery_state.percentage = multi_battery_state.combined_percentage;
|
||||
}
|
||||
}
|
||||
|
||||
const char *battery_get_icon(void)
|
||||
@ -707,6 +782,15 @@ BatteryState systray_get_battery_snapshot(void)
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
MultiBatteryState systray_get_multi_battery_snapshot(void)
|
||||
{
|
||||
MultiBatteryState snapshot;
|
||||
pthread_mutex_lock(&state_mutex);
|
||||
snapshot = multi_battery_state;
|
||||
pthread_mutex_unlock(&state_mutex);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
AudioState systray_get_audio_snapshot(void)
|
||||
{
|
||||
AudioState snapshot;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user