feat: add show desktop and move window to workspace features
build: enhance security with stricter compiler flags and sanitizer build refactor: replace standard memory functions with custom dwn_ variants chore: remove unused wifi and dropdown menu code from systray
This commit is contained in:
parent
58a4a27c8c
commit
8d3dfcbc7e
13
Makefile
13
Makefile
@ -3,7 +3,8 @@
|
||||
|
||||
# Compiler settings
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -O2 -I./include
|
||||
CFLAGS = -Wall -Wextra -Wpedantic -Wshadow -O2 -I./include
|
||||
CFLAGS += -fstack-protector-strong -D_FORTIFY_SOURCE=2
|
||||
LDFLAGS = -lX11 -lXext -lXinerama -lXrandr -lXft -lfontconfig -ldbus-1 -lcurl -lm -lpthread
|
||||
|
||||
# Directories
|
||||
@ -42,7 +43,7 @@ endif
|
||||
# MAIN TARGETS
|
||||
# =============================================================================
|
||||
|
||||
.PHONY: all help clean install uninstall debug run test deps check-deps
|
||||
.PHONY: all help clean install uninstall debug sanitize run test deps check-deps
|
||||
|
||||
# Default target - show help if first time, otherwise build
|
||||
all: check-deps $(TARGET)
|
||||
@ -69,6 +70,7 @@ help:
|
||||
@echo "ALL COMMANDS:"
|
||||
@echo " make - Build DWN (release version)"
|
||||
@echo " make debug - Build with debug symbols"
|
||||
@echo " make sanitize - Build with address/UB sanitizers"
|
||||
@echo " make run - Test in Xephyr window (safe!)"
|
||||
@echo " make install - Install to $(BINDIR)"
|
||||
@echo " make uninstall- Remove from system"
|
||||
@ -91,6 +93,13 @@ debug: CFLAGS += -g -DDEBUG
|
||||
debug: clean $(TARGET)
|
||||
@echo "Debug build complete!"
|
||||
|
||||
# Build with address and undefined behavior sanitizers
|
||||
sanitize: CFLAGS += -g -fsanitize=address,undefined -fno-omit-frame-pointer
|
||||
sanitize: LDFLAGS += -fsanitize=address,undefined
|
||||
sanitize: clean $(TARGET)
|
||||
@echo "Sanitizer build complete!"
|
||||
@echo "Run with: ASAN_OPTIONS=detect_leaks=1 ./$(TARGET)"
|
||||
|
||||
# Link all object files into final binary
|
||||
$(TARGET): $(OBJS) | $(BIN_DIR)
|
||||
@echo "Linking..."
|
||||
|
||||
BIN
build/ai.o
BIN
build/ai.o
Binary file not shown.
Binary file not shown.
BIN
build/atoms.o
BIN
build/atoms.o
Binary file not shown.
Binary file not shown.
BIN
build/cJSON.o
BIN
build/cJSON.o
Binary file not shown.
BIN
build/client.o
BIN
build/client.o
Binary file not shown.
BIN
build/config.o
BIN
build/config.o
Binary file not shown.
Binary file not shown.
BIN
build/demo.o
BIN
build/demo.o
Binary file not shown.
BIN
build/keys.o
BIN
build/keys.o
Binary file not shown.
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/news.o
BIN
build/news.o
Binary file not shown.
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.
BIN
build/util.o
BIN
build/util.o
Binary file not shown.
Binary file not shown.
@ -110,6 +110,7 @@ extern ICCCMAtoms icccm;
|
||||
extern MiscAtoms misc_atoms;
|
||||
|
||||
void atoms_init(Display *display);
|
||||
void atoms_cleanup(void);
|
||||
|
||||
void atoms_setup_ewmh(void);
|
||||
void atoms_update_client_list(void);
|
||||
|
||||
@ -174,6 +174,10 @@ typedef struct {
|
||||
|
||||
bool is_alt_tabbing;
|
||||
Client *alt_tab_client;
|
||||
|
||||
bool desktop_shown;
|
||||
Window desktop_minimized[MAX_CLIENTS];
|
||||
int desktop_minimized_count;
|
||||
} DWNState;
|
||||
|
||||
extern DWNState *dwn;
|
||||
|
||||
@ -86,6 +86,9 @@ void key_snap_right(void);
|
||||
void key_snap_up(void);
|
||||
void key_snap_down(void);
|
||||
void key_start_demo(void);
|
||||
void key_show_desktop(void);
|
||||
void key_move_to_workspace_prev(void);
|
||||
void key_move_to_workspace_next(void);
|
||||
|
||||
void tutorial_start(void);
|
||||
void tutorial_stop(void);
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
#include "dwn.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#define MAX_WIFI_NETWORKS 20
|
||||
#define MAX_TRAY_ICONS 32
|
||||
#define TRAY_ICON_SIZE 22
|
||||
#define TRAY_ICON_SPACING 4
|
||||
@ -41,21 +40,11 @@ extern TrayIcon tray_icons[MAX_TRAY_ICONS];
|
||||
extern int tray_icon_count;
|
||||
extern Window tray_selection_owner;
|
||||
|
||||
typedef struct {
|
||||
char ssid[64];
|
||||
int signal;
|
||||
char security[32];
|
||||
bool connected;
|
||||
} WifiNetwork;
|
||||
|
||||
typedef struct {
|
||||
bool enabled;
|
||||
bool connected;
|
||||
char current_ssid[64];
|
||||
int signal_strength;
|
||||
WifiNetwork networks[MAX_WIFI_NETWORKS];
|
||||
int network_count;
|
||||
long last_scan;
|
||||
} WifiState;
|
||||
|
||||
typedef struct {
|
||||
@ -78,29 +67,15 @@ typedef struct {
|
||||
bool dragging;
|
||||
} VolumeSlider;
|
||||
|
||||
typedef struct {
|
||||
Window window;
|
||||
int x, y;
|
||||
int width, height;
|
||||
int item_count;
|
||||
int hovered_item;
|
||||
bool visible;
|
||||
void (*on_select)(int index);
|
||||
} DropdownMenu;
|
||||
|
||||
extern WifiState wifi_state;
|
||||
extern AudioState audio_state;
|
||||
extern BatteryState battery_state;
|
||||
extern DropdownMenu *wifi_menu;
|
||||
extern VolumeSlider *volume_slider;
|
||||
|
||||
void systray_init(void);
|
||||
void systray_cleanup(void);
|
||||
|
||||
void wifi_update_state(void);
|
||||
void wifi_scan_networks(void);
|
||||
void wifi_connect(const char *ssid);
|
||||
void wifi_disconnect(void);
|
||||
const char *wifi_get_icon(void);
|
||||
|
||||
void audio_update_state(void);
|
||||
@ -120,16 +95,6 @@ void volume_slider_handle_click(VolumeSlider *slider, int x, int y);
|
||||
void volume_slider_handle_motion(VolumeSlider *slider, int x, int y);
|
||||
void volume_slider_handle_release(VolumeSlider *slider);
|
||||
|
||||
DropdownMenu *dropdown_create(int x, int y, int width);
|
||||
void dropdown_destroy(DropdownMenu *menu);
|
||||
void dropdown_show(DropdownMenu *menu);
|
||||
void dropdown_hide(DropdownMenu *menu);
|
||||
void dropdown_add_item(DropdownMenu *menu, const char *label);
|
||||
void dropdown_render(DropdownMenu *menu);
|
||||
int dropdown_hit_test(DropdownMenu *menu, int x, int y);
|
||||
void dropdown_handle_click(DropdownMenu *menu, int x, int y);
|
||||
void dropdown_handle_motion(DropdownMenu *menu, int x, int y);
|
||||
|
||||
void systray_render(Panel *panel, int x, int *width);
|
||||
int systray_get_width(void);
|
||||
void systray_handle_click(int x, int y, int button);
|
||||
|
||||
2
src/ai.c
2
src/ai.c
@ -39,7 +39,7 @@ static size_t write_callback(void *contents, size_t size, size_t nmemb, void *us
|
||||
size_t realsize = size * nmemb;
|
||||
ResponseBuffer *buf = (ResponseBuffer *)userp;
|
||||
|
||||
char *ptr = realloc(buf->data, buf->size + realsize + 1);
|
||||
char *ptr = dwn_realloc(buf->data, buf->size + realsize + 1);
|
||||
if (ptr == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -244,9 +244,11 @@ static void add_to_recent(const char *desktop_id)
|
||||
for (int i = 0; i < launcher_state.recent_count; i++) {
|
||||
if (strcmp(launcher_state.recent[i], desktop_id) == 0) {
|
||||
for (int j = i; j > 0; j--) {
|
||||
strcpy(launcher_state.recent[j], launcher_state.recent[j-1]);
|
||||
memcpy(launcher_state.recent[j], launcher_state.recent[j-1],
|
||||
sizeof(launcher_state.recent[0]));
|
||||
}
|
||||
strcpy(launcher_state.recent[0], desktop_id);
|
||||
snprintf(launcher_state.recent[0], sizeof(launcher_state.recent[0]),
|
||||
"%s", desktop_id);
|
||||
save_recent_apps();
|
||||
return;
|
||||
}
|
||||
@ -257,9 +259,11 @@ static void add_to_recent(const char *desktop_id)
|
||||
}
|
||||
|
||||
for (int i = launcher_state.recent_count - 1; i > 0; i--) {
|
||||
strcpy(launcher_state.recent[i], launcher_state.recent[i-1]);
|
||||
memcpy(launcher_state.recent[i], launcher_state.recent[i-1],
|
||||
sizeof(launcher_state.recent[0]));
|
||||
}
|
||||
strncpy(launcher_state.recent[0], desktop_id, sizeof(launcher_state.recent[0]) - 1);
|
||||
snprintf(launcher_state.recent[0], sizeof(launcher_state.recent[0]),
|
||||
"%s", desktop_id);
|
||||
|
||||
save_recent_apps();
|
||||
}
|
||||
@ -330,16 +334,16 @@ void applauncher_show(void)
|
||||
}
|
||||
|
||||
size_t buf_size = launcher_state.app_count * 140;
|
||||
char *choices = malloc(buf_size);
|
||||
char *choices = dwn_malloc(buf_size);
|
||||
if (choices == NULL) {
|
||||
LOG_ERROR("Failed to allocate memory for app list");
|
||||
return;
|
||||
}
|
||||
choices[0] = '\0';
|
||||
|
||||
bool *added = calloc(launcher_state.app_count, sizeof(bool));
|
||||
bool *added = dwn_calloc(launcher_state.app_count, sizeof(bool));
|
||||
if (added == NULL) {
|
||||
free(choices);
|
||||
dwn_free(choices);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -379,19 +383,19 @@ void applauncher_show(void)
|
||||
choices[pos-1] = '\0';
|
||||
}
|
||||
|
||||
free(added);
|
||||
dwn_free(added);
|
||||
|
||||
char tmp_path[] = "/tmp/dwn_apps_XXXXXX";
|
||||
int tmp_fd = mkstemp(tmp_path);
|
||||
if (tmp_fd < 0) {
|
||||
free(choices);
|
||||
dwn_free(choices);
|
||||
LOG_ERROR("Failed to create temp file for app list");
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t written = write(tmp_fd, choices, strlen(choices));
|
||||
close(tmp_fd);
|
||||
free(choices);
|
||||
dwn_free(choices);
|
||||
|
||||
if (written < 0) {
|
||||
unlink(tmp_path);
|
||||
|
||||
18
src/atoms.c
18
src/atoms.c
@ -16,6 +16,8 @@ EWMHAtoms ewmh;
|
||||
ICCCMAtoms icccm;
|
||||
MiscAtoms misc_atoms;
|
||||
|
||||
static Window ewmh_check_window = None;
|
||||
|
||||
#define ATOM(name) XInternAtom(display, name, False)
|
||||
|
||||
void atoms_init(Display *display)
|
||||
@ -124,7 +126,8 @@ void atoms_setup_ewmh(void)
|
||||
Display *dpy = dwn->display;
|
||||
Window root = dwn->root;
|
||||
|
||||
Window check = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0);
|
||||
ewmh_check_window = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0);
|
||||
Window check = ewmh_check_window;
|
||||
|
||||
XChangeProperty(dpy, root, ewmh.NET_SUPPORTING_WM_CHECK,
|
||||
XA_WINDOW, 32, PropModeReplace,
|
||||
@ -510,3 +513,16 @@ bool atoms_update_wm_state(Window window, Atom state, bool add)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void atoms_cleanup(void)
|
||||
{
|
||||
if (dwn == NULL || dwn->display == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ewmh_check_window != None) {
|
||||
XDestroyWindow(dwn->display, ewmh_check_window);
|
||||
ewmh_check_window = None;
|
||||
LOG_DEBUG("EWMH check window destroyed");
|
||||
}
|
||||
}
|
||||
|
||||
@ -410,7 +410,8 @@ static void demo_phase_workspaces(void)
|
||||
"DWN provides 9 virtual workspaces:\n\n"
|
||||
"- F1 to F9: Switch workspace\n"
|
||||
"- Shift+F1-F9: Move window to workspace\n"
|
||||
"- Ctrl+Alt+Left/Right: Navigate\n\n"
|
||||
"- Ctrl+Alt+Left/Right: Navigate\n"
|
||||
"- Super+Shift+Left/Right: Move window\n\n"
|
||||
"Watch the workspace indicators in the panel...", 0);
|
||||
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
|
||||
demo_next_step();
|
||||
|
||||
124
src/keys.c
124
src/keys.c
@ -92,140 +92,161 @@ static const TutorialStep tutorial_steps[] = {
|
||||
MOD_SUPER, XK_t
|
||||
},
|
||||
{
|
||||
"1/20: Open Terminal",
|
||||
"1/23: Open Terminal",
|
||||
"The terminal is your command center.\n\n"
|
||||
"Press: Ctrl + Alt + T",
|
||||
"Hold Ctrl and Alt, then press T",
|
||||
MOD_CTRL | MOD_ALT, XK_t
|
||||
},
|
||||
{
|
||||
"2/20: App Launcher",
|
||||
"2/23: App Launcher",
|
||||
"Launch any application by name.\n\n"
|
||||
"Press: Alt + F2",
|
||||
"Type app name and press Enter",
|
||||
MOD_ALT, XK_F2
|
||||
},
|
||||
{
|
||||
"3/20: File Manager",
|
||||
"3/23: File Manager",
|
||||
"Open the file manager.\n\n"
|
||||
"Press: Super + E",
|
||||
"",
|
||||
MOD_SUPER, XK_e
|
||||
},
|
||||
{
|
||||
"4/20: Web Browser",
|
||||
"4/23: Web Browser",
|
||||
"Open your default web browser.\n\n"
|
||||
"Press: Super + B",
|
||||
"",
|
||||
MOD_SUPER, XK_b
|
||||
},
|
||||
{
|
||||
"5/20: Switch Windows",
|
||||
"5/23: Switch Windows",
|
||||
"Cycle through open windows.\n\n"
|
||||
"Press: Alt + Tab",
|
||||
"Hold Alt, press Tab repeatedly",
|
||||
MOD_ALT, XK_Tab
|
||||
},
|
||||
{
|
||||
"6/20: Close Window",
|
||||
"6/23: Close Window",
|
||||
"Close the focused window.\n\n"
|
||||
"Press: Alt + F4",
|
||||
"",
|
||||
MOD_ALT, XK_F4
|
||||
},
|
||||
{
|
||||
"7/20: Toggle Maximize",
|
||||
"7/23: Show Desktop",
|
||||
"Minimize all windows to show desktop.\n\n"
|
||||
"Press: Super + D",
|
||||
"Press again to restore windows",
|
||||
MOD_SUPER, XK_d
|
||||
},
|
||||
{
|
||||
"8/23: Toggle Maximize",
|
||||
"Maximize or restore a window.\n\n"
|
||||
"Press: Alt + F10",
|
||||
"",
|
||||
MOD_ALT, XK_F10
|
||||
},
|
||||
{
|
||||
"8/20: Toggle Fullscreen",
|
||||
"9/23: Toggle Fullscreen",
|
||||
"Make a window fullscreen.\n\n"
|
||||
"Press: Alt + F11",
|
||||
"Press again to exit",
|
||||
MOD_ALT, XK_F11
|
||||
},
|
||||
{
|
||||
"9/20: Toggle Floating",
|
||||
"10/23: Toggle Floating",
|
||||
"Make window float above tiled windows.\n\n"
|
||||
"Press: Super + F9",
|
||||
"",
|
||||
MOD_SUPER, XK_F9
|
||||
},
|
||||
{
|
||||
"10/20: Next Workspace",
|
||||
"11/23: Next Workspace",
|
||||
"Switch to the next virtual desktop.\n\n"
|
||||
"Press: Ctrl + Alt + Right",
|
||||
"",
|
||||
MOD_CTRL | MOD_ALT, XK_Right
|
||||
},
|
||||
{
|
||||
"11/20: Previous Workspace",
|
||||
"12/23: Previous Workspace",
|
||||
"Switch to the previous workspace.\n\n"
|
||||
"Press: Ctrl + Alt + Left",
|
||||
"",
|
||||
MOD_CTRL | MOD_ALT, XK_Left
|
||||
},
|
||||
{
|
||||
"12/20: Go to Workspace",
|
||||
"13/23: Go to Workspace",
|
||||
"Jump directly to workspace 1.\n\n"
|
||||
"Press: F1",
|
||||
"Use F1-F9 for workspaces 1-9",
|
||||
0, XK_F1
|
||||
},
|
||||
{
|
||||
"13/20: Cycle Layout",
|
||||
"14/23: Move Window to Prev Workspace",
|
||||
"Send the focused window to previous workspace.\n\n"
|
||||
"Press: Super + Shift + Left",
|
||||
"Window moves, you stay",
|
||||
MOD_SUPER | MOD_SHIFT, XK_Left
|
||||
},
|
||||
{
|
||||
"15/23: Move Window to Next Workspace",
|
||||
"Send the focused window to next workspace.\n\n"
|
||||
"Press: Super + Shift + Right",
|
||||
"Window moves, you stay",
|
||||
MOD_SUPER | MOD_SHIFT, XK_Right
|
||||
},
|
||||
{
|
||||
"16/23: Cycle Layout",
|
||||
"Switch between tiling, floating, monocle.\n\n"
|
||||
"Press: Super + Space",
|
||||
"Try it multiple times!",
|
||||
MOD_SUPER, XK_space
|
||||
},
|
||||
{
|
||||
"14/20: Expand Master",
|
||||
"17/23: Expand Master",
|
||||
"Make the master area larger.\n\n"
|
||||
"Press: Super + L",
|
||||
"",
|
||||
MOD_SUPER, XK_l
|
||||
},
|
||||
{
|
||||
"15/20: Shrink Master",
|
||||
"18/23: Shrink Master",
|
||||
"Make the master area smaller.\n\n"
|
||||
"Press: Super + H",
|
||||
"",
|
||||
MOD_SUPER, XK_h
|
||||
},
|
||||
{
|
||||
"16/20: Add to Master",
|
||||
"19/23: Add to Master",
|
||||
"Increase windows in master area.\n\n"
|
||||
"Press: Super + I",
|
||||
"",
|
||||
MOD_SUPER, XK_i
|
||||
},
|
||||
{
|
||||
"17/20: AI Command",
|
||||
"20/23: AI Command",
|
||||
"Ask AI to run commands for you.\n\n"
|
||||
"Press: Super + A",
|
||||
"Requires OPENROUTER_API_KEY",
|
||||
MOD_SUPER, XK_a
|
||||
},
|
||||
{
|
||||
"18/20: AI Context",
|
||||
"21/23: AI Context",
|
||||
"Show AI analysis of your current task.\n\n"
|
||||
"Press: Super + Shift + A",
|
||||
"Requires OPENROUTER_API_KEY",
|
||||
MOD_SUPER | MOD_SHIFT, XK_a
|
||||
},
|
||||
{
|
||||
"19/20: Exa Search",
|
||||
"22/23: Exa Search",
|
||||
"Semantic web search.\n\n"
|
||||
"Press: Super + Shift + E",
|
||||
"Requires EXA_API_KEY",
|
||||
MOD_SUPER | MOD_SHIFT, XK_e
|
||||
},
|
||||
{
|
||||
"20/20: Show Shortcuts",
|
||||
"23/23: Show Shortcuts",
|
||||
"Display all keyboard shortcuts.\n\n"
|
||||
"Press: Super + S",
|
||||
"Shows complete reference",
|
||||
@ -519,10 +540,14 @@ void keys_setup_defaults(void)
|
||||
keys_bind(MOD_SUPER, XK_l, key_increase_master, "Increase master ratio");
|
||||
keys_bind(MOD_SUPER, XK_h, key_decrease_master, "Decrease master ratio");
|
||||
keys_bind(MOD_SUPER, XK_i, key_increase_master_count, "Increase master count");
|
||||
keys_bind(MOD_SUPER, XK_d, key_decrease_master_count, "Decrease master count");
|
||||
keys_bind(MOD_SUPER, XK_minus, key_decrease_master_count, "Decrease master count");
|
||||
|
||||
keys_bind(MOD_SUPER, XK_d, key_show_desktop, "Show desktop");
|
||||
|
||||
keys_bind(MOD_SUPER, XK_Left, key_snap_left, "Snap window left");
|
||||
keys_bind(MOD_SUPER, XK_Right, key_snap_right, "Snap window right");
|
||||
keys_bind(MOD_SUPER | MOD_SHIFT, XK_Left, key_move_to_workspace_prev, "Move window to previous workspace");
|
||||
keys_bind(MOD_SUPER | MOD_SHIFT, XK_Right, key_move_to_workspace_next, "Move window to next workspace");
|
||||
|
||||
keys_bind(MOD_SUPER, XK_a, key_ai_command, "AI command");
|
||||
keys_bind(MOD_SUPER | MOD_SHIFT, XK_a, key_toggle_ai, "Toggle AI context");
|
||||
@ -725,6 +750,63 @@ void key_decrease_master_count(void)
|
||||
workspace_adjust_master_count(dwn->current_workspace, -1);
|
||||
}
|
||||
|
||||
void key_show_desktop(void)
|
||||
{
|
||||
if (dwn == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dwn->desktop_shown) {
|
||||
dwn->desktop_minimized_count = 0;
|
||||
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
|
||||
if (c->workspace == (unsigned int)dwn->current_workspace &&
|
||||
!client_is_minimized(c) &&
|
||||
dwn->desktop_minimized_count < MAX_CLIENTS) {
|
||||
dwn->desktop_minimized[dwn->desktop_minimized_count++] = c->window;
|
||||
client_minimize(c);
|
||||
}
|
||||
}
|
||||
dwn->desktop_shown = true;
|
||||
} else {
|
||||
for (int i = 0; i < dwn->desktop_minimized_count; i++) {
|
||||
Client *c = client_find_by_window(dwn->desktop_minimized[i]);
|
||||
if (c != NULL && c->workspace == (unsigned int)dwn->current_workspace) {
|
||||
client_restore(c);
|
||||
}
|
||||
}
|
||||
dwn->desktop_minimized_count = 0;
|
||||
dwn->desktop_shown = false;
|
||||
}
|
||||
}
|
||||
|
||||
void key_move_to_workspace_prev(void)
|
||||
{
|
||||
if (dwn == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
int target = dwn->current_workspace - 1;
|
||||
if (target < 0) {
|
||||
target = MAX_WORKSPACES - 1;
|
||||
}
|
||||
|
||||
move_focused_to_workspace(target);
|
||||
}
|
||||
|
||||
void key_move_to_workspace_next(void)
|
||||
{
|
||||
if (dwn == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
int target = dwn->current_workspace + 1;
|
||||
if (target >= MAX_WORKSPACES) {
|
||||
target = 0;
|
||||
}
|
||||
|
||||
move_focused_to_workspace(target);
|
||||
}
|
||||
|
||||
void key_toggle_ai(void)
|
||||
{
|
||||
if (dwn == NULL) {
|
||||
|
||||
14
src/main.c
14
src/main.c
@ -416,15 +416,6 @@ static void handle_button_press(XButtonEvent *ev)
|
||||
}
|
||||
}
|
||||
|
||||
if (wifi_menu != NULL && wifi_menu->visible) {
|
||||
if (ev->window == wifi_menu->window) {
|
||||
dropdown_handle_click(wifi_menu, ev->x, ev->y);
|
||||
return;
|
||||
} else {
|
||||
dropdown_hide(wifi_menu);
|
||||
}
|
||||
}
|
||||
|
||||
Notification *notif = notification_find_by_window(ev->window);
|
||||
if (notif != NULL) {
|
||||
notification_close(notif->id);
|
||||
@ -531,11 +522,6 @@ static void handle_motion_notify(XMotionEvent *ev)
|
||||
return;
|
||||
}
|
||||
|
||||
if (wifi_menu != NULL && wifi_menu->visible && ev->window == wifi_menu->window) {
|
||||
dropdown_handle_motion(wifi_menu, ev->x, ev->y);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dwn->drag_client == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
12
src/news.c
12
src/news.c
@ -54,7 +54,7 @@ static size_t news_curl_write_cb(void *contents, size_t size, size_t nmemb, void
|
||||
size_t realsize = size * nmemb;
|
||||
CurlBuffer *buf = (CurlBuffer *)userp;
|
||||
|
||||
char *ptr = realloc(buf->data, buf->size + realsize + 1);
|
||||
char *ptr = dwn_realloc(buf->data, buf->size + realsize + 1);
|
||||
if (ptr == NULL) {
|
||||
return 0;
|
||||
}
|
||||
@ -374,7 +374,15 @@ static void *news_fetch_thread(void *arg)
|
||||
}
|
||||
|
||||
CurlBuffer buf = {0};
|
||||
buf.data = malloc(1);
|
||||
buf.data = dwn_malloc(1);
|
||||
if (buf.data == NULL) {
|
||||
curl_easy_cleanup(curl);
|
||||
pthread_mutex_lock(&news_mutex);
|
||||
news_state.fetching = false;
|
||||
news_state.has_error = true;
|
||||
pthread_mutex_unlock(&news_mutex);
|
||||
return NULL;
|
||||
}
|
||||
buf.data[0] = '\0';
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, NEWS_API_URL);
|
||||
|
||||
@ -552,8 +552,8 @@ void panel_handle_click(Panel *panel, int x, int y, int button)
|
||||
if (client_is_minimized(c)) {
|
||||
client_restore(c);
|
||||
} else {
|
||||
Workspace *ws = workspace_get(dwn->current_workspace);
|
||||
if (ws != NULL && ws->focused == c) {
|
||||
Workspace *current = workspace_get(dwn->current_workspace);
|
||||
if (current != NULL && current->focused == c) {
|
||||
client_minimize(c);
|
||||
} else {
|
||||
client_focus(c, true);
|
||||
|
||||
348
src/systray.c
348
src/systray.c
@ -35,20 +35,14 @@
|
||||
#define ASCII_CHARGING "+"
|
||||
|
||||
#define SYSTRAY_SPACING 12
|
||||
#define DROPDOWN_ITEM_HEIGHT 28
|
||||
#define DROPDOWN_WIDTH 400
|
||||
#define DROPDOWN_PADDING 8
|
||||
#define SLIDER_WIDTH 30
|
||||
#define SLIDER_HEIGHT 120
|
||||
#define SLIDER_PADDING 8
|
||||
#define SLIDER_KNOB_HEIGHT 8
|
||||
|
||||
#define WIFI_SCAN_INTERVAL 10000
|
||||
|
||||
WifiState wifi_state = {0};
|
||||
AudioState audio_state = {0};
|
||||
BatteryState battery_state = {0};
|
||||
DropdownMenu *wifi_menu = NULL;
|
||||
VolumeSlider *volume_slider = NULL;
|
||||
|
||||
TrayIcon tray_icons[MAX_TRAY_ICONS];
|
||||
@ -174,10 +168,6 @@ void systray_cleanup(void)
|
||||
pthread_join(update_thread, NULL);
|
||||
}
|
||||
|
||||
if (wifi_menu != NULL) {
|
||||
dropdown_destroy(wifi_menu);
|
||||
wifi_menu = NULL;
|
||||
}
|
||||
if (volume_slider != NULL) {
|
||||
volume_slider_destroy(volume_slider);
|
||||
volume_slider = NULL;
|
||||
@ -309,82 +299,6 @@ void wifi_update_state(void)
|
||||
{
|
||||
}
|
||||
|
||||
void wifi_scan_networks(void)
|
||||
{
|
||||
FILE *fp;
|
||||
char line[512];
|
||||
|
||||
WifiNetwork temp_networks[MAX_WIFI_NETWORKS];
|
||||
int temp_count = 0;
|
||||
long scan_time = get_time_ms();
|
||||
|
||||
fp = popen("nmcli -t -f SSID,SIGNAL,SECURITY,IN-USE dev wifi list 2>/dev/null", "r");
|
||||
if (fp == NULL) {
|
||||
LOG_WARN("Failed to scan WiFi networks");
|
||||
return;
|
||||
}
|
||||
|
||||
while (fgets(line, sizeof(line), fp) != NULL && temp_count < MAX_WIFI_NETWORKS) {
|
||||
line[strcspn(line, "\n")] = '\0';
|
||||
|
||||
char *ssid = strtok(line, ":");
|
||||
char *signal = strtok(NULL, ":");
|
||||
char *security = strtok(NULL, ":");
|
||||
char *in_use = strtok(NULL, ":");
|
||||
|
||||
if (ssid != NULL && strlen(ssid) > 0) {
|
||||
WifiNetwork *net = &temp_networks[temp_count];
|
||||
strncpy(net->ssid, ssid, sizeof(net->ssid) - 1);
|
||||
net->ssid[sizeof(net->ssid) - 1] = '\0';
|
||||
net->signal = signal ? atoi(signal) : 0;
|
||||
strncpy(net->security, security ? security : "", sizeof(net->security) - 1);
|
||||
net->security[sizeof(net->security) - 1] = '\0';
|
||||
net->connected = (in_use != NULL && in_use[0] == '*');
|
||||
temp_count++;
|
||||
}
|
||||
}
|
||||
pclose(fp);
|
||||
|
||||
pthread_mutex_lock(&state_mutex);
|
||||
memcpy(wifi_state.networks, temp_networks, sizeof(temp_networks));
|
||||
wifi_state.network_count = temp_count;
|
||||
wifi_state.last_scan = scan_time;
|
||||
pthread_mutex_unlock(&state_mutex);
|
||||
|
||||
LOG_DEBUG("Scanned %d WiFi networks", temp_count);
|
||||
}
|
||||
|
||||
void wifi_connect(const char *ssid)
|
||||
{
|
||||
if (ssid == NULL || strlen(ssid) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char cmd[256];
|
||||
snprintf(cmd, sizeof(cmd), "nmcli dev wifi connect \"%s\" &", ssid);
|
||||
|
||||
char msg[128];
|
||||
snprintf(msg, sizeof(msg), "Connecting to %s...", ssid);
|
||||
notification_show("WiFi", "Connecting", msg, NULL, 3000);
|
||||
|
||||
spawn_async(cmd);
|
||||
|
||||
pthread_mutex_lock(&state_mutex);
|
||||
wifi_state.connected = false;
|
||||
pthread_mutex_unlock(&state_mutex);
|
||||
}
|
||||
|
||||
void wifi_disconnect(void)
|
||||
{
|
||||
spawn_async("nmcli dev disconnect wlan0 &");
|
||||
|
||||
pthread_mutex_lock(&state_mutex);
|
||||
wifi_state.connected = false;
|
||||
pthread_mutex_unlock(&state_mutex);
|
||||
|
||||
notification_show("WiFi", "Disconnected", "WiFi connection closed", NULL, 2000);
|
||||
}
|
||||
|
||||
const char *wifi_get_icon(void)
|
||||
{
|
||||
if (!wifi_state.enabled || !wifi_state.connected) {
|
||||
@ -615,227 +529,6 @@ void volume_slider_handle_release(VolumeSlider *slider)
|
||||
}
|
||||
|
||||
|
||||
DropdownMenu *dropdown_create(int x, int y, int width)
|
||||
{
|
||||
if (dwn == NULL || dwn->display == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DropdownMenu *menu = dwn_calloc(1, sizeof(DropdownMenu));
|
||||
menu->x = x;
|
||||
menu->y = y;
|
||||
menu->width = width;
|
||||
menu->height = 0;
|
||||
menu->item_count = 0;
|
||||
menu->hovered_item = -1;
|
||||
menu->visible = false;
|
||||
menu->on_select = NULL;
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
void dropdown_destroy(DropdownMenu *menu)
|
||||
{
|
||||
if (menu == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (menu->window != None && dwn != NULL && dwn->display != NULL) {
|
||||
XDestroyWindow(dwn->display, menu->window);
|
||||
}
|
||||
|
||||
dwn_free(menu);
|
||||
}
|
||||
|
||||
void dropdown_show(DropdownMenu *menu)
|
||||
{
|
||||
if (menu == NULL || dwn == NULL || dwn->display == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
wifi_scan_networks();
|
||||
|
||||
menu->item_count = wifi_state.network_count;
|
||||
menu->height = menu->item_count * DROPDOWN_ITEM_HEIGHT + 2 * DROPDOWN_PADDING;
|
||||
|
||||
if (menu->height < DROPDOWN_ITEM_HEIGHT + 2 * DROPDOWN_PADDING) {
|
||||
menu->height = DROPDOWN_ITEM_HEIGHT + 2 * DROPDOWN_PADDING;
|
||||
menu->item_count = 1;
|
||||
}
|
||||
|
||||
int max_text_width = 0;
|
||||
for (int i = 0; i < wifi_state.network_count; i++) {
|
||||
WifiNetwork *net = &wifi_state.networks[i];
|
||||
char label[128];
|
||||
snprintf(label, sizeof(label), "%s %d%%", net->ssid, net->signal);
|
||||
int text_width = get_text_width(label);
|
||||
if (text_width > max_text_width) {
|
||||
max_text_width = text_width;
|
||||
}
|
||||
}
|
||||
int min_width = 250;
|
||||
int calculated_width = max_text_width + 2 * DROPDOWN_PADDING + 30;
|
||||
menu->width = (calculated_width > min_width) ? calculated_width : min_width;
|
||||
|
||||
if (menu->window == None) {
|
||||
XSetWindowAttributes swa;
|
||||
swa.override_redirect = True;
|
||||
swa.background_pixel = dwn->config->colors.panel_bg;
|
||||
swa.border_pixel = dwn->config->colors.border_focused;
|
||||
swa.event_mask = ExposureMask | ButtonPressMask | PointerMotionMask |
|
||||
LeaveWindowMask | EnterWindowMask;
|
||||
|
||||
menu->window = XCreateWindow(dwn->display, dwn->root,
|
||||
menu->x, menu->y,
|
||||
menu->width, menu->height,
|
||||
0,
|
||||
CopyFromParent, InputOutput, CopyFromParent,
|
||||
CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWEventMask,
|
||||
&swa);
|
||||
} else {
|
||||
XMoveResizeWindow(dwn->display, menu->window,
|
||||
menu->x, menu->y, menu->width, menu->height);
|
||||
}
|
||||
|
||||
menu->visible = true;
|
||||
XMapRaised(dwn->display, menu->window);
|
||||
dropdown_render(menu);
|
||||
}
|
||||
|
||||
void dropdown_hide(DropdownMenu *menu)
|
||||
{
|
||||
if (menu == NULL || menu->window == None) {
|
||||
return;
|
||||
}
|
||||
|
||||
menu->visible = false;
|
||||
XUnmapWindow(dwn->display, menu->window);
|
||||
}
|
||||
|
||||
void dropdown_render(DropdownMenu *menu)
|
||||
{
|
||||
if (menu == NULL || menu->window == None || !menu->visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dwn == NULL || dwn->display == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
Display *dpy = dwn->display;
|
||||
const ColorScheme *colors = config_get_colors();
|
||||
|
||||
int font_height = 12;
|
||||
if (dwn->xft_font != NULL) {
|
||||
font_height = dwn->xft_font->ascent;
|
||||
} else if (dwn->font != NULL) {
|
||||
font_height = dwn->font->ascent;
|
||||
}
|
||||
int text_y_offset = (DROPDOWN_ITEM_HEIGHT + font_height) / 2;
|
||||
|
||||
XSetForeground(dpy, dwn->gc, colors->panel_bg);
|
||||
XFillRectangle(dpy, menu->window, dwn->gc, 0, 0, menu->width, menu->height);
|
||||
|
||||
if (wifi_state.network_count == 0) {
|
||||
draw_utf8_text(menu->window, DROPDOWN_PADDING, DROPDOWN_PADDING + text_y_offset,
|
||||
"No networks found", colors->workspace_inactive);
|
||||
} else {
|
||||
for (int i = 0; i < wifi_state.network_count && i < MAX_WIFI_NETWORKS; i++) {
|
||||
WifiNetwork *net = &wifi_state.networks[i];
|
||||
int y = DROPDOWN_PADDING + i * DROPDOWN_ITEM_HEIGHT;
|
||||
|
||||
if (i == menu->hovered_item) {
|
||||
XSetForeground(dpy, dwn->gc, colors->workspace_active);
|
||||
XFillRectangle(dpy, menu->window, dwn->gc,
|
||||
2, y, menu->width - 4, DROPDOWN_ITEM_HEIGHT);
|
||||
}
|
||||
|
||||
unsigned long text_color = (i == menu->hovered_item) ? colors->panel_bg : colors->panel_fg;
|
||||
|
||||
char label[80];
|
||||
if (net->connected) {
|
||||
snprintf(label, sizeof(label), "> %s", net->ssid);
|
||||
} else {
|
||||
snprintf(label, sizeof(label), " %s", net->ssid);
|
||||
}
|
||||
|
||||
if (strlen(label) > 25) {
|
||||
label[22] = '.';
|
||||
label[23] = '.';
|
||||
label[24] = '.';
|
||||
label[25] = '\0';
|
||||
}
|
||||
|
||||
draw_utf8_text(menu->window, DROPDOWN_PADDING, y + text_y_offset, label, text_color);
|
||||
|
||||
char signal[8];
|
||||
snprintf(signal, sizeof(signal), "%d%%", net->signal);
|
||||
int signal_width = get_text_width(signal);
|
||||
int signal_x = menu->width - DROPDOWN_PADDING - signal_width;
|
||||
|
||||
unsigned long signal_color = (i == menu->hovered_item) ? colors->panel_bg : colors->workspace_inactive;
|
||||
draw_utf8_text(menu->window, signal_x, y + text_y_offset, signal, signal_color);
|
||||
}
|
||||
}
|
||||
|
||||
XFlush(dpy);
|
||||
}
|
||||
|
||||
int dropdown_hit_test(DropdownMenu *menu, int x, int y)
|
||||
{
|
||||
(void)x;
|
||||
|
||||
if (menu == NULL || !menu->visible) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (y < DROPDOWN_PADDING || y >= menu->height - DROPDOWN_PADDING) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int item = (y - DROPDOWN_PADDING) / DROPDOWN_ITEM_HEIGHT;
|
||||
if (item >= 0 && item < menu->item_count) {
|
||||
return item;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void dropdown_handle_click(DropdownMenu *menu, int x, int y)
|
||||
{
|
||||
if (menu == NULL || !menu->visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
int item = dropdown_hit_test(menu, x, y);
|
||||
if (item >= 0 && item < wifi_state.network_count) {
|
||||
WifiNetwork *net = &wifi_state.networks[item];
|
||||
|
||||
if (net->connected) {
|
||||
wifi_disconnect();
|
||||
} else {
|
||||
wifi_connect(net->ssid);
|
||||
}
|
||||
|
||||
dropdown_hide(menu);
|
||||
}
|
||||
}
|
||||
|
||||
void dropdown_handle_motion(DropdownMenu *menu, int x, int y)
|
||||
{
|
||||
if (menu == NULL || !menu->visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
int old_hovered = menu->hovered_item;
|
||||
menu->hovered_item = dropdown_hit_test(menu, x, y);
|
||||
|
||||
if (menu->hovered_item != old_hovered) {
|
||||
dropdown_render(menu);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int systray_get_width(void)
|
||||
{
|
||||
pthread_mutex_lock(&state_mutex);
|
||||
@ -955,34 +648,7 @@ void systray_handle_click(int x, int y, int button)
|
||||
return;
|
||||
}
|
||||
|
||||
if (widget == 0) {
|
||||
if (volume_slider != NULL && volume_slider->visible) {
|
||||
volume_slider_hide(volume_slider);
|
||||
}
|
||||
|
||||
if (button == 1) {
|
||||
if (wifi_menu == NULL) {
|
||||
int panel_height = config_get_panel_height();
|
||||
wifi_menu = dropdown_create(wifi_icon_x, panel_height, DROPDOWN_WIDTH);
|
||||
}
|
||||
|
||||
if (wifi_menu->visible) {
|
||||
dropdown_hide(wifi_menu);
|
||||
} else {
|
||||
wifi_menu->x = wifi_icon_x;
|
||||
wifi_menu->y = config_get_panel_height();
|
||||
dropdown_show(wifi_menu);
|
||||
}
|
||||
} else if (button == 3) {
|
||||
if (wifi_state.connected) {
|
||||
wifi_disconnect();
|
||||
}
|
||||
}
|
||||
} else if (widget == 1) {
|
||||
if (wifi_menu != NULL && wifi_menu->visible) {
|
||||
dropdown_hide(wifi_menu);
|
||||
}
|
||||
|
||||
if (widget == 1) {
|
||||
if (button == 1) {
|
||||
if (volume_slider == NULL) {
|
||||
int panel_height = config_get_panel_height();
|
||||
@ -1019,18 +685,6 @@ void systray_handle_click(int x, int y, int button)
|
||||
|
||||
void systray_update(void)
|
||||
{
|
||||
|
||||
if (wifi_menu != NULL && wifi_menu->visible) {
|
||||
long now = get_time_ms();
|
||||
pthread_mutex_lock(&state_mutex);
|
||||
long last_scan = wifi_state.last_scan;
|
||||
pthread_mutex_unlock(&state_mutex);
|
||||
|
||||
if (now - last_scan >= WIFI_SCAN_INTERVAL) {
|
||||
wifi_scan_networks();
|
||||
dropdown_render(wifi_menu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user