style: remove comments from header files

This commit is contained in:
retoor 2025-12-28 05:01:46 +01:00
parent a6b9fa3469
commit c76a620012
38 changed files with 167 additions and 1481 deletions

View File

@ -10,7 +10,6 @@
#include "dwn.h" #include "dwn.h"
#include <stdbool.h> #include <stdbool.h>
/* AI request states */
typedef enum { typedef enum {
AI_STATE_IDLE, AI_STATE_IDLE,
AI_STATE_PENDING, AI_STATE_PENDING,
@ -18,7 +17,6 @@ typedef enum {
AI_STATE_ERROR AI_STATE_ERROR
} AIState; } AIState;
/* AI request structure */
typedef struct AIRequest { typedef struct AIRequest {
char *prompt; char *prompt;
char *response; char *response;
@ -28,7 +26,6 @@ typedef struct AIRequest {
struct AIRequest *next; struct AIRequest *next;
} AIRequest; } AIRequest;
/* AI context for window analysis */
typedef struct { typedef struct {
char focused_window[256]; char focused_window[256];
char focused_class[64]; char focused_class[64];
@ -36,40 +33,32 @@ typedef struct {
int window_count; int window_count;
} AIContext; } AIContext;
/* Initialization */
bool ai_init(void); bool ai_init(void);
void ai_cleanup(void); void ai_cleanup(void);
bool ai_is_available(void); bool ai_is_available(void);
/* API calls */
AIRequest *ai_send_request(const char *prompt, void (*callback)(AIRequest *)); AIRequest *ai_send_request(const char *prompt, void (*callback)(AIRequest *));
void ai_cancel_request(AIRequest *req); void ai_cancel_request(AIRequest *req);
void ai_process_pending(void); void ai_process_pending(void);
/* Context analysis */
void ai_update_context(void); void ai_update_context(void);
const char *ai_analyze_task(void); const char *ai_analyze_task(void);
const char *ai_suggest_window(void); const char *ai_suggest_window(void);
const char *ai_suggest_app(void); const char *ai_suggest_app(void);
/* Command palette */
void ai_show_command_palette(void); void ai_show_command_palette(void);
void ai_execute_command(const char *command); void ai_execute_command(const char *command);
/* Smart features */
void ai_auto_organize_workspace(void); void ai_auto_organize_workspace(void);
void ai_suggest_layout(void); void ai_suggest_layout(void);
void ai_analyze_workflow(void); void ai_analyze_workflow(void);
/* Notification intelligence */
bool ai_should_show_notification(const char *app, const char *summary); bool ai_should_show_notification(const char *app, const char *summary);
int ai_notification_priority(const char *app, const char *summary); int ai_notification_priority(const char *app, const char *summary);
/* Performance monitoring */
void ai_monitor_performance(void); void ai_monitor_performance(void);
const char *ai_performance_suggestion(void); const char *ai_performance_suggestion(void);
/* Exa semantic search */
typedef struct { typedef struct {
char title[256]; char title[256];
char url[512]; char url[512];
@ -81,7 +70,7 @@ typedef struct ExaRequest {
char *query; char *query;
ExaSearchResult results[10]; ExaSearchResult results[10];
int result_count; int result_count;
int state; /* Uses AIState enum */ int state;
void (*callback)(struct ExaRequest *req); void (*callback)(struct ExaRequest *req);
void *user_data; void *user_data;
struct ExaRequest *next; struct ExaRequest *next;
@ -92,4 +81,4 @@ ExaRequest *exa_search(const char *query, void (*callback)(ExaRequest *));
void exa_process_pending(void); void exa_process_pending(void);
void exa_show_app_launcher(void); void exa_show_app_launcher(void);
#endif /* DWN_AI_H */ #endif

View File

@ -9,41 +9,33 @@
#include <stdbool.h> #include <stdbool.h>
/* Maximum applications to track */
#define MAX_APPS 512 #define MAX_APPS 512
#define MAX_RECENT_APPS 10 #define MAX_RECENT_APPS 10
/* Application entry from .desktop file */
typedef struct { typedef struct {
char name[128]; /* Display name */ char name[128];
char exec[512]; /* Command to execute */ char exec[512];
char icon[128]; /* Icon name (unused for now) */ char icon[128];
char desktop_id[256]; /* Desktop file basename for tracking */ char desktop_id[256];
bool terminal; /* Run in terminal */ bool terminal;
bool hidden; /* Should be hidden */ bool hidden;
} AppEntry; } AppEntry;
/* Application launcher state */
typedef struct { typedef struct {
AppEntry apps[MAX_APPS]; AppEntry apps[MAX_APPS];
int app_count; int app_count;
char recent[MAX_RECENT_APPS][256]; /* Desktop IDs of recent apps */ char recent[MAX_RECENT_APPS][256];
int recent_count; int recent_count;
} AppLauncherState; } AppLauncherState;
/* Initialize the app launcher (scans .desktop files) */
void applauncher_init(void); void applauncher_init(void);
/* Cleanup resources */
void applauncher_cleanup(void); void applauncher_cleanup(void);
/* Rescan .desktop files */
void applauncher_refresh(void); void applauncher_refresh(void);
/* Show the application launcher (dmenu-based) */
void applauncher_show(void); void applauncher_show(void);
/* Launch an application by desktop_id */
void applauncher_launch(const char *desktop_id); void applauncher_launch(const char *desktop_id);
#endif /* DWN_APPLAUNCHER_H */ #endif

View File

@ -10,9 +10,7 @@
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <stdbool.h> #include <stdbool.h>
/* EWMH (Extended Window Manager Hints) atoms */
typedef struct { typedef struct {
/* Root window properties */
Atom NET_SUPPORTED; Atom NET_SUPPORTED;
Atom NET_SUPPORTING_WM_CHECK; Atom NET_SUPPORTING_WM_CHECK;
Atom NET_CLIENT_LIST; Atom NET_CLIENT_LIST;
@ -25,7 +23,6 @@ typedef struct {
Atom NET_ACTIVE_WINDOW; Atom NET_ACTIVE_WINDOW;
Atom NET_WORKAREA; Atom NET_WORKAREA;
/* Client window properties */
Atom NET_WM_NAME; Atom NET_WM_NAME;
Atom NET_WM_VISIBLE_NAME; Atom NET_WM_VISIBLE_NAME;
Atom NET_WM_DESKTOP; Atom NET_WM_DESKTOP;
@ -36,7 +33,6 @@ typedef struct {
Atom NET_WM_STRUT_PARTIAL; Atom NET_WM_STRUT_PARTIAL;
Atom NET_WM_PID; Atom NET_WM_PID;
/* Window types */
Atom NET_WM_WINDOW_TYPE_DESKTOP; Atom NET_WM_WINDOW_TYPE_DESKTOP;
Atom NET_WM_WINDOW_TYPE_DOCK; Atom NET_WM_WINDOW_TYPE_DOCK;
Atom NET_WM_WINDOW_TYPE_TOOLBAR; Atom NET_WM_WINDOW_TYPE_TOOLBAR;
@ -47,7 +43,6 @@ typedef struct {
Atom NET_WM_WINDOW_TYPE_NORMAL; Atom NET_WM_WINDOW_TYPE_NORMAL;
Atom NET_WM_WINDOW_TYPE_NOTIFICATION; Atom NET_WM_WINDOW_TYPE_NOTIFICATION;
/* Window states */
Atom NET_WM_STATE_MODAL; Atom NET_WM_STATE_MODAL;
Atom NET_WM_STATE_STICKY; Atom NET_WM_STATE_STICKY;
Atom NET_WM_STATE_MAXIMIZED_VERT; Atom NET_WM_STATE_MAXIMIZED_VERT;
@ -62,7 +57,6 @@ typedef struct {
Atom NET_WM_STATE_DEMANDS_ATTENTION; Atom NET_WM_STATE_DEMANDS_ATTENTION;
Atom NET_WM_STATE_FOCUSED; Atom NET_WM_STATE_FOCUSED;
/* Actions */
Atom NET_WM_ACTION_MOVE; Atom NET_WM_ACTION_MOVE;
Atom NET_WM_ACTION_RESIZE; Atom NET_WM_ACTION_RESIZE;
Atom NET_WM_ACTION_MINIMIZE; Atom NET_WM_ACTION_MINIMIZE;
@ -74,14 +68,12 @@ typedef struct {
Atom NET_WM_ACTION_CHANGE_DESKTOP; Atom NET_WM_ACTION_CHANGE_DESKTOP;
Atom NET_WM_ACTION_CLOSE; Atom NET_WM_ACTION_CLOSE;
/* Client messages */
Atom NET_CLOSE_WINDOW; Atom NET_CLOSE_WINDOW;
Atom NET_MOVERESIZE_WINDOW; Atom NET_MOVERESIZE_WINDOW;
Atom NET_WM_MOVERESIZE; Atom NET_WM_MOVERESIZE;
Atom NET_REQUEST_FRAME_EXTENTS; Atom NET_REQUEST_FRAME_EXTENTS;
Atom NET_FRAME_EXTENTS; Atom NET_FRAME_EXTENTS;
/* System tray */
Atom NET_SYSTEM_TRAY_OPCODE; Atom NET_SYSTEM_TRAY_OPCODE;
Atom NET_SYSTEM_TRAY_S0; Atom NET_SYSTEM_TRAY_S0;
Atom MANAGER; Atom MANAGER;
@ -89,7 +81,6 @@ typedef struct {
Atom XEMBED_INFO; Atom XEMBED_INFO;
} EWMHAtoms; } EWMHAtoms;
/* ICCCM (Inter-Client Communication Conventions Manual) atoms */
typedef struct { typedef struct {
Atom WM_PROTOCOLS; Atom WM_PROTOCOLS;
Atom WM_DELETE_WINDOW; Atom WM_DELETE_WINDOW;
@ -103,7 +94,6 @@ typedef struct {
Atom WM_WINDOW_ROLE; Atom WM_WINDOW_ROLE;
} ICCCMAtoms; } ICCCMAtoms;
/* Other useful atoms */
typedef struct { typedef struct {
Atom UTF8_STRING; Atom UTF8_STRING;
Atom COMPOUND_TEXT; Atom COMPOUND_TEXT;
@ -113,15 +103,12 @@ typedef struct {
Atom DWN_RESTART; Atom DWN_RESTART;
} MiscAtoms; } MiscAtoms;
/* Global atom containers */
extern EWMHAtoms ewmh; extern EWMHAtoms ewmh;
extern ICCCMAtoms icccm; extern ICCCMAtoms icccm;
extern MiscAtoms misc_atoms; extern MiscAtoms misc_atoms;
/* Initialization */
void atoms_init(Display *display); void atoms_init(Display *display);
/* EWMH root window setup */
void atoms_setup_ewmh(void); void atoms_setup_ewmh(void);
void atoms_update_client_list(void); void atoms_update_client_list(void);
void atoms_update_desktop_names(void); void atoms_update_desktop_names(void);
@ -129,7 +116,6 @@ void atoms_set_current_desktop(int desktop);
void atoms_set_active_window(Window window); void atoms_set_active_window(Window window);
void atoms_set_number_of_desktops(int count); void atoms_set_number_of_desktops(int count);
/* Window property helpers */
bool atoms_get_window_type(Window window, Atom *type); bool atoms_get_window_type(Window window, Atom *type);
bool atoms_get_window_state(Window window, Atom **states, int *count); bool atoms_get_window_state(Window window, Atom **states, int *count);
bool atoms_set_window_state(Window window, Atom *states, int count); bool atoms_set_window_state(Window window, Atom *states, int count);
@ -138,12 +124,10 @@ bool atoms_set_window_desktop(Window window, int desktop);
char *atoms_get_window_name(Window window); char *atoms_get_window_name(Window window);
bool atoms_get_wm_class(Window window, char *class_name, char *instance_name, size_t len); bool atoms_get_wm_class(Window window, char *class_name, char *instance_name, size_t len);
/* Protocol helpers */
bool atoms_window_supports_protocol(Window window, Atom protocol); bool atoms_window_supports_protocol(Window window, Atom protocol);
void atoms_send_protocol(Window window, Atom protocol, Time timestamp); void atoms_send_protocol(Window window, Atom protocol, Time timestamp);
/* Client message sending */
void atoms_send_client_message(Window window, Atom message_type, void atoms_send_client_message(Window window, Atom message_type,
long data0, long data1, long data2, long data3, long data4); long data0, long data1, long data2, long data3, long data4);
#endif /* DWN_ATOMS_H */ #endif

View File

@ -10,17 +10,14 @@
#include "dwn.h" #include "dwn.h"
#include <stdbool.h> #include <stdbool.h>
/* Client creation and destruction */
Client *client_create(Window window); Client *client_create(Window window);
void client_destroy(Client *client); void client_destroy(Client *client);
/* Client management */
Client *client_manage(Window window); Client *client_manage(Window window);
void client_unmanage(Client *client); void client_unmanage(Client *client);
Client *client_find_by_window(Window window); Client *client_find_by_window(Window window);
Client *client_find_by_frame(Window frame); Client *client_find_by_frame(Window frame);
/* Client state */
void client_focus(Client *client); void client_focus(Client *client);
void client_unfocus(Client *client); void client_unfocus(Client *client);
void client_raise(Client *client); void client_raise(Client *client);
@ -28,14 +25,12 @@ void client_lower(Client *client);
void client_minimize(Client *client); void client_minimize(Client *client);
void client_restore(Client *client); void client_restore(Client *client);
/* Client geometry */
void client_move(Client *client, int x, int y); void client_move(Client *client, int x, int y);
void client_resize(Client *client, int width, int height); void client_resize(Client *client, int width, int height);
void client_move_resize(Client *client, int x, int y, int width, int height); void client_move_resize(Client *client, int x, int y, int width, int height);
void client_configure(Client *client); void client_configure(Client *client);
void client_apply_size_hints(Client *client, int *width, int *height); void client_apply_size_hints(Client *client, int *width, int *height);
/* Client properties */
void client_update_title(Client *client); void client_update_title(Client *client);
void client_update_class(Client *client); void client_update_class(Client *client);
void client_set_fullscreen(Client *client, bool fullscreen); void client_set_fullscreen(Client *client, bool fullscreen);
@ -43,7 +38,6 @@ void client_toggle_fullscreen(Client *client);
void client_set_floating(Client *client, bool floating); void client_set_floating(Client *client, bool floating);
void client_toggle_floating(Client *client); void client_toggle_floating(Client *client);
/* Window type checking */
bool client_is_floating(Client *client); bool client_is_floating(Client *client);
bool client_is_fullscreen(Client *client); bool client_is_fullscreen(Client *client);
bool client_is_minimized(Client *client); bool client_is_minimized(Client *client);
@ -51,31 +45,26 @@ bool client_is_dialog(Window window);
bool client_is_dock(Window window); bool client_is_dock(Window window);
bool client_is_desktop(Window window); bool client_is_desktop(Window window);
/* Frame management */
void client_create_frame(Client *client); void client_create_frame(Client *client);
void client_destroy_frame(Client *client); void client_destroy_frame(Client *client);
void client_reparent_to_frame(Client *client); void client_reparent_to_frame(Client *client);
void client_reparent_from_frame(Client *client); void client_reparent_from_frame(Client *client);
/* Visibility */
void client_show(Client *client); void client_show(Client *client);
void client_hide(Client *client); void client_hide(Client *client);
bool client_is_visible(Client *client); bool client_is_visible(Client *client);
/* Close handling */
void client_close(Client *client); void client_close(Client *client);
void client_kill(Client *client); void client_kill(Client *client);
/* List operations */
void client_add_to_list(Client *client); void client_add_to_list(Client *client);
void client_remove_from_list(Client *client); void client_remove_from_list(Client *client);
int client_count(void); int client_count(void);
int client_count_on_workspace(int workspace); int client_count_on_workspace(int workspace);
/* Iteration */
Client *client_get_next(Client *client); Client *client_get_next(Client *client);
Client *client_get_prev(Client *client); Client *client_get_prev(Client *client);
Client *client_get_first(void); Client *client_get_first(void);
Client *client_get_last(void); Client *client_get_last(void);
#endif /* DWN_CLIENT_H */ #endif

View File

@ -10,7 +10,6 @@
#include "dwn.h" #include "dwn.h"
#include <stdbool.h> #include <stdbool.h>
/* Color configuration */
typedef struct { typedef struct {
unsigned long panel_bg; unsigned long panel_bg;
unsigned long panel_fg; unsigned long panel_fg;
@ -27,16 +26,13 @@ typedef struct {
unsigned long notification_fg; unsigned long notification_fg;
} ColorScheme; } ColorScheme;
/* Configuration structure */
struct Config { struct Config {
/* General */
char terminal[128]; char terminal[128];
char launcher[128]; char launcher[128];
char file_manager[128]; char file_manager[128];
FocusMode focus_mode; FocusMode focus_mode;
bool show_decorations; bool show_decorations;
/* Appearance */
int border_width; int border_width;
int title_height; int title_height;
int panel_height; int panel_height;
@ -44,34 +40,28 @@ struct Config {
char font_name[128]; char font_name[128];
ColorScheme colors; ColorScheme colors;
/* Layout */
float default_master_ratio; float default_master_ratio;
int default_master_count; int default_master_count;
LayoutType default_layout; LayoutType default_layout;
/* Panels */
bool top_panel_enabled; bool top_panel_enabled;
bool bottom_panel_enabled; bool bottom_panel_enabled;
/* AI */
char openrouter_api_key[256]; char openrouter_api_key[256];
char exa_api_key[256]; char exa_api_key[256];
char ai_model[64]; char ai_model[64];
bool ai_enabled; bool ai_enabled;
/* Paths */
char config_path[512]; char config_path[512];
char log_path[512]; char log_path[512];
}; };
/* Configuration functions */
Config *config_create(void); Config *config_create(void);
void config_destroy(Config *cfg); void config_destroy(Config *cfg);
bool config_load(Config *cfg, const char *path); bool config_load(Config *cfg, const char *path);
bool config_reload(Config *cfg); bool config_reload(Config *cfg);
void config_set_defaults(Config *cfg); void config_set_defaults(Config *cfg);
/* Getters for commonly used values */
const char *config_get_terminal(void); const char *config_get_terminal(void);
const char *config_get_launcher(void); const char *config_get_launcher(void);
int config_get_border_width(void); int config_get_border_width(void);
@ -80,9 +70,8 @@ int config_get_panel_height(void);
int config_get_gap(void); int config_get_gap(void);
const ColorScheme *config_get_colors(void); const ColorScheme *config_get_colors(void);
/* INI parsing helpers */
typedef void (*ConfigCallback)(const char *section, const char *key, typedef void (*ConfigCallback)(const char *section, const char *key,
const char *value, void *user_data); const char *value, void *user_data);
bool config_parse_ini(const char *path, ConfigCallback callback, void *user_data); bool config_parse_ini(const char *path, ConfigCallback callback, void *user_data);
#endif /* DWN_CONFIG_H */ #endif

View File

@ -10,7 +10,6 @@
#include "dwn.h" #include "dwn.h"
#include <stdbool.h> #include <stdbool.h>
/* Button types */
typedef enum { typedef enum {
BUTTON_CLOSE, BUTTON_CLOSE,
BUTTON_MAXIMIZE, BUTTON_MAXIMIZE,
@ -18,32 +17,26 @@ typedef enum {
BUTTON_COUNT BUTTON_COUNT
} ButtonType; } ButtonType;
/* Button areas for hit testing */
typedef struct { typedef struct {
int x, y; int x, y;
int width, height; int width, height;
} ButtonArea; } ButtonArea;
/* Decoration initialization */
void decorations_init(void); void decorations_init(void);
void decorations_cleanup(void); void decorations_cleanup(void);
/* Rendering */
void decorations_render(Client *client, bool focused); void decorations_render(Client *client, bool focused);
void decorations_render_title_bar(Client *client, bool focused); void decorations_render_title_bar(Client *client, bool focused);
void decorations_render_buttons(Client *client, bool focused); void decorations_render_buttons(Client *client, bool focused);
void decorations_render_border(Client *client, bool focused); void decorations_render_border(Client *client, bool focused);
/* Hit testing */
ButtonType decorations_hit_test_button(Client *client, int x, int y); ButtonType decorations_hit_test_button(Client *client, int x, int y);
bool decorations_hit_test_title_bar(Client *client, int x, int y); bool decorations_hit_test_title_bar(Client *client, int x, int y);
bool decorations_hit_test_resize_area(Client *client, int x, int y, int *direction); bool decorations_hit_test_resize_area(Client *client, int x, int y, int *direction);
/* Button actions */
void decorations_button_press(Client *client, ButtonType button); void decorations_button_press(Client *client, ButtonType button);
/* Text rendering */
void decorations_draw_text(Window window, GC gc, int x, int y, void decorations_draw_text(Window window, GC gc, int x, int y,
const char *text, unsigned long color); const char *text, unsigned long color);
#endif /* DWN_DECORATIONS_H */ #endif

View File

@ -16,24 +16,20 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
/* Version */
#define DWN_VERSION "1.0.0" #define DWN_VERSION "1.0.0"
#define DWN_NAME "DWN" #define DWN_NAME "DWN"
/* Limits */
#define MAX_CLIENTS 256 #define MAX_CLIENTS 256
#define MAX_WORKSPACES 9 #define MAX_WORKSPACES 9
#define MAX_MONITORS 8 #define MAX_MONITORS 8
#define MAX_NOTIFICATIONS 32 #define MAX_NOTIFICATIONS 32
#define MAX_KEYBINDINGS 64 #define MAX_KEYBINDINGS 64
/* Default dimensions */
#define DEFAULT_BORDER_WIDTH 2 #define DEFAULT_BORDER_WIDTH 2
#define DEFAULT_TITLE_HEIGHT 24 #define DEFAULT_TITLE_HEIGHT 24
#define DEFAULT_PANEL_HEIGHT 28 #define DEFAULT_PANEL_HEIGHT 28
#define DEFAULT_GAP 4 #define DEFAULT_GAP 4
/* Common error/status codes */
typedef enum { typedef enum {
DWN_OK = 0, DWN_OK = 0,
DWN_ERROR = -1, DWN_ERROR = -1,
@ -44,7 +40,6 @@ typedef enum {
DWN_ERROR_IO = -6 DWN_ERROR_IO = -6
} DwnStatus; } DwnStatus;
/* Layout types */
typedef enum { typedef enum {
LAYOUT_TILING, LAYOUT_TILING,
LAYOUT_FLOATING, LAYOUT_FLOATING,
@ -52,13 +47,11 @@ typedef enum {
LAYOUT_COUNT LAYOUT_COUNT
} LayoutType; } LayoutType;
/* Focus modes */
typedef enum { typedef enum {
FOCUS_CLICK, FOCUS_CLICK,
FOCUS_FOLLOW FOCUS_FOLLOW
} FocusMode; } FocusMode;
/* Client state flags */
typedef enum { typedef enum {
CLIENT_NORMAL = 0, CLIENT_NORMAL = 0,
CLIENT_FLOATING = (1 << 0), CLIENT_FLOATING = (1 << 0),
@ -68,31 +61,28 @@ typedef enum {
CLIENT_STICKY = (1 << 4) CLIENT_STICKY = (1 << 4)
} ClientFlags; } ClientFlags;
/* Forward declarations */
typedef struct Client Client; typedef struct Client Client;
typedef struct Workspace Workspace; typedef struct Workspace Workspace;
typedef struct Monitor Monitor; typedef struct Monitor Monitor;
typedef struct Panel Panel; typedef struct Panel Panel;
typedef struct Config Config; typedef struct Config Config;
/* Client structure - represents a managed window */
struct Client { struct Client {
Window window; /* Application window */ Window window;
Window frame; /* Frame window (decoration) */ Window frame;
int x, y; /* Position */ int x, y;
int width, height; /* Size */ int width, height;
int old_x, old_y; /* Previous position (for floating restore) */ int old_x, old_y;
int old_width, old_height; int old_width, old_height;
int border_width; int border_width;
uint32_t flags; /* ClientFlags bitmask */ uint32_t flags;
unsigned int workspace; /* Current workspace (0-8) */ unsigned int workspace;
char title[256]; /* Window title */ char title[256];
char class[64]; /* Window class */ char class[64];
Client *next; /* Linked list */ Client *next;
Client *prev; Client *prev;
}; };
/* Monitor structure - represents a physical display */
struct Monitor { struct Monitor {
int x, y; int x, y;
int width, height; int width, height;
@ -100,17 +90,15 @@ struct Monitor {
bool primary; bool primary;
}; };
/* Workspace structure */
struct Workspace { struct Workspace {
Client *clients; /* Head of client list */ Client *clients;
Client *focused; /* Currently focused client */ Client *focused;
LayoutType layout; /* Current layout */ LayoutType layout;
float master_ratio; /* Ratio for master area in tiling */ float master_ratio;
int master_count; /* Number of windows in master area */ int master_count;
char name[32]; /* Workspace name */ char name[32];
}; };
/* Panel widget types */
typedef enum { typedef enum {
WIDGET_WORKSPACES, WIDGET_WORKSPACES,
WIDGET_TASKBAR, WIDGET_TASKBAR,
@ -120,7 +108,6 @@ typedef enum {
WIDGET_SEPARATOR WIDGET_SEPARATOR
} WidgetType; } WidgetType;
/* Global state - singleton pattern */
typedef struct { typedef struct {
Display *display; Display *display;
int screen; int screen;
@ -128,36 +115,28 @@ typedef struct {
int screen_width; int screen_width;
int screen_height; int screen_height;
/* Monitors */
Monitor monitors[MAX_MONITORS]; Monitor monitors[MAX_MONITORS];
int monitor_count; int monitor_count;
/* Workspaces */
Workspace workspaces[MAX_WORKSPACES]; Workspace workspaces[MAX_WORKSPACES];
int current_workspace; int current_workspace;
/* Clients */ Client *client_list;
Client *client_list; /* All clients */
int client_count; int client_count;
/* Panels */
Panel *top_panel; Panel *top_panel;
Panel *bottom_panel; Panel *bottom_panel;
/* Configuration */
Config *config; Config *config;
/* State */
bool running; bool running;
bool ai_enabled; bool ai_enabled;
/* Graphics contexts */
GC gc; GC gc;
XFontStruct *font; XFontStruct *font;
XftFont *xft_font; /* Xft font for UTF-8 rendering */ XftFont *xft_font;
Colormap colormap; Colormap colormap;
/* Drag state */
Client *drag_client; Client *drag_client;
int drag_start_x, drag_start_y; int drag_start_x, drag_start_y;
int drag_orig_x, drag_orig_y; int drag_orig_x, drag_orig_y;
@ -165,16 +144,13 @@ typedef struct {
bool resizing; bool resizing;
} DWNState; } DWNState;
/* Global state accessor */
extern DWNState *dwn; extern DWNState *dwn;
/* Core functions */
int dwn_init(void); int dwn_init(void);
void dwn_cleanup(void); void dwn_cleanup(void);
void dwn_run(void); void dwn_run(void);
void dwn_quit(void); void dwn_quit(void);
/* Event handlers */
void dwn_handle_event(XEvent *ev); void dwn_handle_event(XEvent *ev);
#endif /* DWN_H */ #endif

View File

@ -11,16 +11,13 @@
#include <stdbool.h> #include <stdbool.h>
#include <X11/keysym.h> #include <X11/keysym.h>
/* Modifier masks */
#define MOD_ALT Mod1Mask #define MOD_ALT Mod1Mask
#define MOD_CTRL ControlMask #define MOD_CTRL ControlMask
#define MOD_SHIFT ShiftMask #define MOD_SHIFT ShiftMask
#define MOD_SUPER Mod4Mask #define MOD_SUPER Mod4Mask
/* Key binding callback type */
typedef void (*KeyCallback)(void); typedef void (*KeyCallback)(void);
/* Key binding structure */
typedef struct { typedef struct {
unsigned int modifiers; unsigned int modifiers;
KeySym keysym; KeySym keysym;
@ -28,26 +25,21 @@ typedef struct {
const char *description; const char *description;
} KeyBinding; } KeyBinding;
/* Initialization */
void keys_init(void); void keys_init(void);
void keys_cleanup(void); void keys_cleanup(void);
void keys_grab_all(void); void keys_grab_all(void);
void keys_ungrab_all(void); void keys_ungrab_all(void);
/* Key binding registration */
void keys_bind(unsigned int modifiers, KeySym keysym, void keys_bind(unsigned int modifiers, KeySym keysym,
KeyCallback callback, const char *description); KeyCallback callback, const char *description);
void keys_unbind(unsigned int modifiers, KeySym keysym); void keys_unbind(unsigned int modifiers, KeySym keysym);
void keys_clear_all(void); void keys_clear_all(void);
/* Key event handling */
void keys_handle_press(XKeyEvent *ev); void keys_handle_press(XKeyEvent *ev);
void keys_handle_release(XKeyEvent *ev); void keys_handle_release(XKeyEvent *ev);
/* Default key bindings */
void keys_setup_defaults(void); void keys_setup_defaults(void);
/* Key binding callbacks */
void key_spawn_terminal(void); void key_spawn_terminal(void);
void key_spawn_launcher(void); void key_spawn_launcher(void);
void key_spawn_file_manager(void); void key_spawn_file_manager(void);
@ -89,11 +81,10 @@ void key_ai_command(void);
void key_show_shortcuts(void); void key_show_shortcuts(void);
void key_start_tutorial(void); void key_start_tutorial(void);
/* Tutorial system */
void tutorial_start(void); void tutorial_start(void);
void tutorial_stop(void); void tutorial_stop(void);
void tutorial_next_step(void); void tutorial_next_step(void);
void tutorial_check_key(unsigned int modifiers, KeySym keysym); void tutorial_check_key(unsigned int modifiers, KeySym keysym);
bool tutorial_is_active(void); bool tutorial_is_active(void);
#endif /* DWN_KEYS_H */ #endif

View File

@ -9,18 +9,15 @@
#include "dwn.h" #include "dwn.h"
/* Layout arrangement */
void layout_arrange(int workspace); void layout_arrange(int workspace);
void layout_arrange_tiling(int workspace); void layout_arrange_tiling(int workspace);
void layout_arrange_floating(int workspace); void layout_arrange_floating(int workspace);
void layout_arrange_monocle(int workspace); void layout_arrange_monocle(int workspace);
/* Layout helpers */
int layout_get_usable_area(int *x, int *y, int *width, int *height); int layout_get_usable_area(int *x, int *y, int *width, int *height);
int layout_count_tiled_clients(int workspace); int layout_count_tiled_clients(int workspace);
/* Layout names */
const char *layout_get_name(LayoutType layout); const char *layout_get_name(LayoutType layout);
const char *layout_get_symbol(LayoutType layout); const char *layout_get_symbol(LayoutType layout);
#endif /* DWN_LAYOUT_H */ #endif

View File

@ -10,18 +10,15 @@
#include "dwn.h" #include "dwn.h"
#include <stdbool.h> #include <stdbool.h>
/* Maximum articles to cache */
#define MAX_NEWS_ARTICLES 50 #define MAX_NEWS_ARTICLES 50
#define NEWS_API_URL "https://news.app.molodetz.nl/api" #define NEWS_API_URL "https://news.app.molodetz.nl/api"
/* Sentiment classification */
typedef enum { typedef enum {
SENTIMENT_NEUTRAL = 0, SENTIMENT_NEUTRAL = 0,
SENTIMENT_POSITIVE, SENTIMENT_POSITIVE,
SENTIMENT_NEGATIVE SENTIMENT_NEGATIVE
} NewsSentiment; } NewsSentiment;
/* News article */
typedef struct { typedef struct {
char title[256]; char title[256];
char content[1024]; char content[1024];
@ -31,46 +28,39 @@ typedef struct {
float sentiment_score; float sentiment_score;
} NewsArticle; } NewsArticle;
/* News ticker state */
typedef struct { typedef struct {
NewsArticle articles[MAX_NEWS_ARTICLES]; NewsArticle articles[MAX_NEWS_ARTICLES];
int article_count; int article_count;
int current_article; /* Currently displayed article index */ int current_article;
double scroll_offset; /* Sub-pixel offset for smooth scrolling */ double scroll_offset;
bool fetching; /* Currently fetching from API */ bool fetching;
bool has_error; /* Last fetch failed */ bool has_error;
long last_fetch; /* Timestamp of last fetch */ long last_fetch;
long last_scroll_update; /* Timestamp of last scroll update */ long last_scroll_update;
bool interactive_mode; /* User is navigating with up/down */ bool interactive_mode;
int display_widths[MAX_NEWS_ARTICLES]; /* Cached text widths */ int display_widths[MAX_NEWS_ARTICLES];
int total_width; /* Total scrollable width */ int total_width;
int render_x; /* X position where news starts rendering */ int render_x;
int render_width; /* Width of news render area */ int render_width;
bool widths_dirty; /* Need to recalculate widths */ bool widths_dirty;
} NewsState; } NewsState;
/* Global state */
extern NewsState news_state; extern NewsState news_state;
/* Initialization */
void news_init(void); void news_init(void);
void news_cleanup(void); void news_cleanup(void);
/* Fetching */
void news_fetch_async(void); void news_fetch_async(void);
void news_update(void); /* Called from main loop */ void news_update(void);
/* Navigation */
void news_next_article(void); void news_next_article(void);
void news_prev_article(void); void news_prev_article(void);
void news_open_current(void); void news_open_current(void);
/* Rendering */
void news_render(Panel *panel, int x, int max_width, int *used_width); void news_render(Panel *panel, int x, int max_width, int *used_width);
void news_handle_click(int x, int y); void news_handle_click(int x, int y);
/* Thread-safe access */
void news_lock(void); void news_lock(void);
void news_unlock(void); void news_unlock(void);
#endif /* DWN_NEWS_H */ #endif

View File

@ -11,42 +11,36 @@
#include <stdbool.h> #include <stdbool.h>
#include <dbus/dbus.h> #include <dbus/dbus.h>
/* Notification urgency levels */
typedef enum { typedef enum {
NOTIFY_URGENCY_LOW, NOTIFY_URGENCY_LOW,
NOTIFY_URGENCY_NORMAL, NOTIFY_URGENCY_NORMAL,
NOTIFY_URGENCY_CRITICAL NOTIFY_URGENCY_CRITICAL
} NotifyUrgency; } NotifyUrgency;
/* Notification structure */
typedef struct Notification { typedef struct Notification {
uint32_t id; uint32_t id;
char app_name[64]; char app_name[64];
char summary[512]; /* Larger summary for AI responses */ char summary[512];
char *body; /* Dynamically allocated - unlimited size */ char *body;
size_t body_len; /* Length of body text */ size_t body_len;
char icon[256]; char icon[256];
int timeout; /* -1 = default, 0 = never, >0 = milliseconds */ int timeout;
NotifyUrgency urgency; NotifyUrgency urgency;
long expire_time; /* Timestamp when notification should disappear */ long expire_time;
Window window; /* X11 window for rendering */ Window window;
int width; /* Dynamic width based on content */ int width;
int height; /* Dynamic height based on content */ int height;
struct Notification *next; struct Notification *next;
} Notification; } Notification;
/* D-Bus connection */
extern DBusConnection *dbus_conn; extern DBusConnection *dbus_conn;
/* Initialization */
bool notifications_init(void); bool notifications_init(void);
void notifications_cleanup(void); void notifications_cleanup(void);
/* D-Bus handling */
void notifications_process_messages(void); void notifications_process_messages(void);
bool notifications_register_service(void); bool notifications_register_service(void);
/* Notification management */
uint32_t notification_show(const char *app_name, const char *summary, uint32_t notification_show(const char *app_name, const char *summary,
const char *body, const char *icon, int timeout); const char *body, const char *icon, int timeout);
void notification_close(uint32_t id); void notification_close(uint32_t id);
@ -54,21 +48,18 @@ void notification_close_all(void);
Notification *notification_find(uint32_t id); Notification *notification_find(uint32_t id);
Notification *notification_find_by_window(Window window); Notification *notification_find_by_window(Window window);
/* Rendering */
void notification_render(Notification *notif); void notification_render(Notification *notif);
void notifications_render_all(void); void notifications_render_all(void);
void notifications_update(void); void notifications_update(void);
void notifications_position(void); void notifications_position(void);
void notifications_raise_all(void); void notifications_raise_all(void);
/* D-Bus method handlers */
DBusHandlerResult notifications_handle_message(DBusConnection *conn, DBusHandlerResult notifications_handle_message(DBusConnection *conn,
DBusMessage *msg, DBusMessage *msg,
void *user_data); void *user_data);
/* Server info */
void notifications_get_server_info(const char **name, const char **vendor, void notifications_get_server_info(const char **name, const char **vendor,
const char **version, const char **spec_version); const char **version, const char **spec_version);
void notifications_get_capabilities(const char ***caps, int *count); void notifications_get_capabilities(const char ***caps, int *count);
#endif /* DWN_NOTIFICATIONS_H */ #endif

View File

@ -10,29 +10,25 @@
#include "dwn.h" #include "dwn.h"
#include <stdbool.h> #include <stdbool.h>
/* Panel position */
typedef enum { typedef enum {
PANEL_TOP, PANEL_TOP,
PANEL_BOTTOM PANEL_BOTTOM
} PanelPosition; } PanelPosition;
/* Panel structure */
struct Panel { struct Panel {
Window window; Window window;
PanelPosition position; PanelPosition position;
int x, y; int x, y;
int width, height; int width, height;
bool visible; bool visible;
Pixmap buffer; /* Double buffering */ Pixmap buffer;
}; };
/* Panel initialization */
Panel *panel_create(PanelPosition position); Panel *panel_create(PanelPosition position);
void panel_destroy(Panel *panel); void panel_destroy(Panel *panel);
void panels_init(void); void panels_init(void);
void panels_cleanup(void); void panels_cleanup(void);
/* Panel rendering */
void panel_render(Panel *panel); void panel_render(Panel *panel);
void panel_render_all(void); void panel_render_all(void);
void panel_render_workspaces(Panel *panel, int x, int *width); void panel_render_workspaces(Panel *panel, int x, int *width);
@ -42,25 +38,20 @@ void panel_render_systray(Panel *panel, int x, int *width);
void panel_render_layout_indicator(Panel *panel, int x, int *width); void panel_render_layout_indicator(Panel *panel, int x, int *width);
void panel_render_ai_status(Panel *panel, int x, int *width); void panel_render_ai_status(Panel *panel, int x, int *width);
/* Panel interaction */
void panel_handle_click(Panel *panel, int x, int y, int button); void panel_handle_click(Panel *panel, int x, int y, int button);
int panel_hit_test_workspace(Panel *panel, int x, int y); int panel_hit_test_workspace(Panel *panel, int x, int y);
Client *panel_hit_test_taskbar(Panel *panel, int x, int y); Client *panel_hit_test_taskbar(Panel *panel, int x, int y);
/* Panel visibility */
void panel_show(Panel *panel); void panel_show(Panel *panel);
void panel_hide(Panel *panel); void panel_hide(Panel *panel);
void panel_toggle(Panel *panel); void panel_toggle(Panel *panel);
/* Clock updates */
void panel_update_clock(void); void panel_update_clock(void);
/* System stats updates */
void panel_update_system_stats(void); void panel_update_system_stats(void);
/* System tray */
void panel_init_systray(void); void panel_init_systray(void);
void panel_add_systray_icon(Window icon); void panel_add_systray_icon(Window icon);
void panel_remove_systray_icon(Window icon); void panel_remove_systray_icon(Window icon);
#endif /* DWN_PANEL_H */ #endif

View File

@ -10,18 +10,15 @@
#include "dwn.h" #include "dwn.h"
#include <stdbool.h> #include <stdbool.h>
/* Maximum number of WiFi networks to show */
#define MAX_WIFI_NETWORKS 20 #define MAX_WIFI_NETWORKS 20
/* WiFi network info */
typedef struct { typedef struct {
char ssid[64]; char ssid[64];
int signal; /* Signal strength 0-100 */ int signal;
char security[32]; /* Security type (WPA2, etc.) */ char security[32];
bool connected; bool connected;
} WifiNetwork; } WifiNetwork;
/* WiFi state */
typedef struct { typedef struct {
bool enabled; bool enabled;
bool connected; bool connected;
@ -29,24 +26,21 @@ typedef struct {
int signal_strength; int signal_strength;
WifiNetwork networks[MAX_WIFI_NETWORKS]; WifiNetwork networks[MAX_WIFI_NETWORKS];
int network_count; int network_count;
long last_scan; /* Timestamp of last scan */ long last_scan;
} WifiState; } WifiState;
/* Audio state */
typedef struct { typedef struct {
int volume; /* 0-100 */ int volume;
bool muted; bool muted;
} AudioState; } AudioState;
/* Battery state */
typedef struct { typedef struct {
bool present; /* Battery exists */ bool present;
bool charging; /* Currently charging */ bool charging;
int percentage; /* 0-100 */ int percentage;
int time_remaining; /* Minutes remaining */ int time_remaining;
} BatteryState; } BatteryState;
/* Volume slider popup */
typedef struct { typedef struct {
Window window; Window window;
int x, y; int x, y;
@ -55,7 +49,6 @@ typedef struct {
bool dragging; bool dragging;
} VolumeSlider; } VolumeSlider;
/* Dropdown menu */
typedef struct { typedef struct {
Window window; Window window;
int x, y; int x, y;
@ -66,35 +59,29 @@ typedef struct {
void (*on_select)(int index); void (*on_select)(int index);
} DropdownMenu; } DropdownMenu;
/* System tray state */
extern WifiState wifi_state; extern WifiState wifi_state;
extern AudioState audio_state; extern AudioState audio_state;
extern BatteryState battery_state; extern BatteryState battery_state;
extern DropdownMenu *wifi_menu; extern DropdownMenu *wifi_menu;
extern VolumeSlider *volume_slider; extern VolumeSlider *volume_slider;
/* Initialization */
void systray_init(void); void systray_init(void);
void systray_cleanup(void); void systray_cleanup(void);
/* WiFi functions */
void wifi_update_state(void); void wifi_update_state(void);
void wifi_scan_networks(void); void wifi_scan_networks(void);
void wifi_connect(const char *ssid); void wifi_connect(const char *ssid);
void wifi_disconnect(void); void wifi_disconnect(void);
const char *wifi_get_icon(void); const char *wifi_get_icon(void);
/* Audio functions */
void audio_update_state(void); void audio_update_state(void);
void audio_set_volume(int volume); void audio_set_volume(int volume);
void audio_toggle_mute(void); void audio_toggle_mute(void);
const char *audio_get_icon(void); const char *audio_get_icon(void);
/* Battery functions */
void battery_update_state(void); void battery_update_state(void);
const char *battery_get_icon(void); const char *battery_get_icon(void);
/* Volume slider functions */
VolumeSlider *volume_slider_create(int x, int y); VolumeSlider *volume_slider_create(int x, int y);
void volume_slider_destroy(VolumeSlider *slider); void volume_slider_destroy(VolumeSlider *slider);
void volume_slider_show(VolumeSlider *slider); void volume_slider_show(VolumeSlider *slider);
@ -104,7 +91,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_motion(VolumeSlider *slider, int x, int y);
void volume_slider_handle_release(VolumeSlider *slider); void volume_slider_handle_release(VolumeSlider *slider);
/* Dropdown menu functions */
DropdownMenu *dropdown_create(int x, int y, int width); DropdownMenu *dropdown_create(int x, int y, int width);
void dropdown_destroy(DropdownMenu *menu); void dropdown_destroy(DropdownMenu *menu);
void dropdown_show(DropdownMenu *menu); void dropdown_show(DropdownMenu *menu);
@ -115,21 +101,17 @@ int dropdown_hit_test(DropdownMenu *menu, int x, int y);
void dropdown_handle_click(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 dropdown_handle_motion(DropdownMenu *menu, int x, int y);
/* Panel rendering for systray */
void systray_render(Panel *panel, int x, int *width); void systray_render(Panel *panel, int x, int *width);
int systray_get_width(void); int systray_get_width(void);
void systray_handle_click(int x, int y, int button); void systray_handle_click(int x, int y, int button);
int systray_hit_test(int x); /* Returns: 0=wifi, 1=audio, -1=none */ int systray_hit_test(int x);
/* Periodic update */
void systray_update(void); void systray_update(void);
/* Thread-safe state access */
void systray_lock(void); void systray_lock(void);
void systray_unlock(void); void systray_unlock(void);
/* Thread-safe state snapshots (copies state under lock) */
BatteryState systray_get_battery_snapshot(void); BatteryState systray_get_battery_snapshot(void);
AudioState systray_get_audio_snapshot(void); AudioState systray_get_audio_snapshot(void);
#endif /* DWN_SYSTRAY_H */ #endif

View File

@ -12,11 +12,9 @@
#include <stdarg.h> #include <stdarg.h>
#include <assert.h> #include <assert.h>
/* Contract assertion macro - use for programmer errors */
#define DWN_ASSERT(cond) assert(cond) #define DWN_ASSERT(cond) assert(cond)
#define DWN_ASSERT_MSG(cond, msg) assert((cond) && (msg)) #define DWN_ASSERT_MSG(cond, msg) assert((cond) && (msg))
/* Logging levels */
typedef enum { typedef enum {
LOG_DEBUG, LOG_DEBUG,
LOG_INFO, LOG_INFO,
@ -24,51 +22,43 @@ typedef enum {
LOG_ERROR LOG_ERROR
} LogLevel; } LogLevel;
/* Async Logging - non-blocking with max file size (5MB) and rotation */
void log_init(const char *log_file); void log_init(const char *log_file);
void log_close(void); void log_close(void);
void log_set_level(LogLevel level); void log_set_level(LogLevel level);
void log_flush(void); /* Force flush pending logs (call before exit/crash) */ void log_flush(void);
void log_msg(LogLevel level, const char *fmt, ...); void log_msg(LogLevel level, const char *fmt, ...);
/* Convenience macros */
#define LOG_DEBUG(...) log_msg(LOG_DEBUG, __VA_ARGS__) #define LOG_DEBUG(...) log_msg(LOG_DEBUG, __VA_ARGS__)
#define LOG_INFO(...) log_msg(LOG_INFO, __VA_ARGS__) #define LOG_INFO(...) log_msg(LOG_INFO, __VA_ARGS__)
#define LOG_WARN(...) log_msg(LOG_WARN, __VA_ARGS__) #define LOG_WARN(...) log_msg(LOG_WARN, __VA_ARGS__)
#define LOG_ERROR(...) log_msg(LOG_ERROR, __VA_ARGS__) #define LOG_ERROR(...) log_msg(LOG_ERROR, __VA_ARGS__)
/* Memory allocation with error checking */
void *dwn_malloc(size_t size); void *dwn_malloc(size_t size);
void *dwn_calloc(size_t nmemb, size_t size); void *dwn_calloc(size_t nmemb, size_t size);
void *dwn_realloc(void *ptr, size_t size); void *dwn_realloc(void *ptr, size_t size);
char *dwn_strdup(const char *s); char *dwn_strdup(const char *s);
void dwn_free(void *ptr); void dwn_free(void *ptr);
void secure_wipe(void *ptr, size_t size); /* Securely wipe sensitive data */ void secure_wipe(void *ptr, size_t size);
/* String utilities */
char *str_trim(char *str); char *str_trim(char *str);
bool str_starts_with(const char *str, const char *prefix); bool str_starts_with(const char *str, const char *prefix);
bool str_ends_with(const char *str, const char *suffix); bool str_ends_with(const char *str, const char *suffix);
int str_split(char *str, char delim, char **parts, int max_parts); int str_split(char *str, char delim, char **parts, int max_parts);
char *shell_escape(const char *str); /* Escape string for safe shell use */ char *shell_escape(const char *str);
/* File utilities */
bool file_exists(const char *path); bool file_exists(const char *path);
char *file_read_all(const char *path); char *file_read_all(const char *path);
bool file_write_all(const char *path, const char *content); bool file_write_all(const char *path, const char *content);
char *expand_path(const char *path); char *expand_path(const char *path);
/* Color utilities */
unsigned long parse_color(const char *color_str); unsigned long parse_color(const char *color_str);
void color_to_rgb(unsigned long color, int *r, int *g, int *b); void color_to_rgb(unsigned long color, int *r, int *g, int *b);
/* Time utilities */
long get_time_ms(void); long get_time_ms(void);
void sleep_ms(int ms); void sleep_ms(int ms);
/* Process utilities */
int spawn(const char *cmd); int spawn(const char *cmd);
int spawn_async(const char *cmd); int spawn_async(const char *cmd);
char *spawn_capture(const char *cmd); char *spawn_capture(const char *cmd);
#endif /* DWN_UTIL_H */ #endif

View File

@ -10,28 +10,23 @@
#include "dwn.h" #include "dwn.h"
#include <stdbool.h> #include <stdbool.h>
/* Workspace initialization */
void workspace_init(void); void workspace_init(void);
void workspace_cleanup(void); void workspace_cleanup(void);
/* Workspace access */
Workspace *workspace_get(int index); Workspace *workspace_get(int index);
Workspace *workspace_get_current(void); Workspace *workspace_get_current(void);
int workspace_get_current_index(void); int workspace_get_current_index(void);
/* Workspace switching */
void workspace_switch(int index); void workspace_switch(int index);
void workspace_switch_next(void); void workspace_switch_next(void);
void workspace_switch_prev(void); void workspace_switch_prev(void);
/* Client management within workspaces */
void workspace_add_client(int workspace, Client *client); void workspace_add_client(int workspace, Client *client);
void workspace_remove_client(int workspace, Client *client); void workspace_remove_client(int workspace, Client *client);
void workspace_move_client(Client *client, int new_workspace); void workspace_move_client(Client *client, int new_workspace);
Client *workspace_get_first_client(int workspace); Client *workspace_get_first_client(int workspace);
Client *workspace_get_focused_client(int workspace); Client *workspace_get_focused_client(int workspace);
/* Layout */
void workspace_set_layout(int workspace, LayoutType layout); void workspace_set_layout(int workspace, LayoutType layout);
LayoutType workspace_get_layout(int workspace); LayoutType workspace_get_layout(int workspace);
void workspace_cycle_layout(int workspace); void workspace_cycle_layout(int workspace);
@ -40,23 +35,19 @@ void workspace_adjust_master_ratio(int workspace, float delta);
void workspace_set_master_count(int workspace, int count); void workspace_set_master_count(int workspace, int count);
void workspace_adjust_master_count(int workspace, int delta); void workspace_adjust_master_count(int workspace, int delta);
/* Arrangement */
void workspace_arrange(int workspace); void workspace_arrange(int workspace);
void workspace_arrange_current(void); void workspace_arrange_current(void);
/* Visibility */
void workspace_show(int workspace); void workspace_show(int workspace);
void workspace_hide(int workspace); void workspace_hide(int workspace);
/* Properties */
void workspace_set_name(int workspace, const char *name); void workspace_set_name(int workspace, const char *name);
const char *workspace_get_name(int workspace); const char *workspace_get_name(int workspace);
int workspace_client_count(int workspace); int workspace_client_count(int workspace);
bool workspace_is_empty(int workspace); bool workspace_is_empty(int workspace);
/* Focus cycling within workspace */
void workspace_focus_next(void); void workspace_focus_next(void);
void workspace_focus_prev(void); void workspace_focus_prev(void);
void workspace_focus_master(void); void workspace_focus_master(void);
#endif /* DWN_WORKSPACE_H */ #endif

View File

@ -37,7 +37,6 @@
</div> </div>
</nav> </nav>
</header> </header>
<main> <main>
<section class="hero" style="padding: 8rem 0 4rem;"> <section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content"> <div class="container hero-content">
@ -47,7 +46,6 @@
</p> </p>
</div> </div>
</section> </section>
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<div class="alert alert-info" style="margin-bottom: 2rem;"> <div class="alert alert-info" style="margin-bottom: 2rem;">
@ -55,7 +53,6 @@
<p style="margin: 0;">AI features are completely optional and require external API keys. <p style="margin: 0;">AI features are completely optional and require external API keys.
DWN works perfectly without them.</p> DWN works perfectly without them.</p>
</div> </div>
<h2>Overview</h2> <h2>Overview</h2>
<p> <p>
DWN integrates with two AI services to provide intelligent desktop assistance: DWN integrates with two AI services to provide intelligent desktop assistance:
@ -64,7 +61,6 @@
<li><strong>OpenRouter API</strong> - Powers the AI command palette and context analysis</li> <li><strong>OpenRouter API</strong> - Powers the AI command palette and context analysis</li>
<li><strong>Exa API</strong> - Provides semantic web search capabilities</li> <li><strong>Exa API</strong> - Provides semantic web search capabilities</li>
</ul> </ul>
<div class="features-grid"> <div class="features-grid">
<div class="feature-card"> <div class="feature-card">
<div class="feature-icon">&#129302;</div> <div class="feature-icon">&#129302;</div>
@ -88,14 +84,11 @@
<p style="margin-top: 1rem;"><kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>E</kbd></p> <p style="margin-top: 1rem;"><kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>E</kbd></p>
</div> </div>
</div> </div>
<!-- Setup OpenRouter -->
<h2 id="openrouter" style="margin-top: 4rem;">Setting Up OpenRouter</h2> <h2 id="openrouter" style="margin-top: 4rem;">Setting Up OpenRouter</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;"> <p style="color: var(--text-muted); margin-bottom: 1.5rem;">
OpenRouter provides access to multiple AI models through a single API. OpenRouter provides access to multiple AI models through a single API.
You can use free models or paid ones depending on your needs. You can use free models or paid ones depending on your needs.
</p> </p>
<div class="steps"> <div class="steps">
<div class="step"> <div class="step">
<div class="step-number">1</div> <div class="step-number">1</div>
@ -105,7 +98,6 @@
and create a free account to get your API key.</p> and create a free account to get your API key.</p>
</div> </div>
</div> </div>
<div class="step"> <div class="step">
<div class="step-number">2</div> <div class="step-number">2</div>
<div class="step-content"> <div class="step-content">
@ -118,7 +110,6 @@
<pre><code>export OPENROUTER_API_KEY="sk-or-v1-your-key-here"</code></pre> <pre><code>export OPENROUTER_API_KEY="sk-or-v1-your-key-here"</code></pre>
</div> </div>
</div> </div>
<div class="step"> <div class="step">
<div class="step-number">3</div> <div class="step-number">3</div>
<div class="step-content"> <div class="step-content">
@ -136,7 +127,6 @@ model = google/gemini-2.0-flash-exp:free</code></pre>
</div> </div>
</div> </div>
</div> </div>
<div class="card" style="margin-top: 2rem;"> <div class="card" style="margin-top: 2rem;">
<h3>Recommended Free Models</h3> <h3>Recommended Free Models</h3>
<div class="table-wrapper"> <div class="table-wrapper">
@ -168,14 +158,11 @@ model = google/gemini-2.0-flash-exp:free</code></pre>
</table> </table>
</div> </div>
</div> </div>
<!-- AI Command Palette -->
<h2 id="command-palette" style="margin-top: 4rem;">AI Command Palette</h2> <h2 id="command-palette" style="margin-top: 4rem;">AI Command Palette</h2>
<p> <p>
Press <kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd> to open the command palette. Press <kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd> to open the command palette.
Type natural language commands and press Enter. Type natural language commands and press Enter.
</p> </p>
<h3>Supported Commands</h3> <h3>Supported Commands</h3>
<div class="table-wrapper"> <div class="table-wrapper">
<table> <table>
@ -215,19 +202,15 @@ model = google/gemini-2.0-flash-exp:free</code></pre>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="alert alert-success" style="margin-top: 1.5rem;"> <div class="alert alert-success" style="margin-top: 1.5rem;">
<strong class="alert-title">Pro Tip</strong> <strong class="alert-title">Pro Tip</strong>
<p style="margin: 0;">The AI understands context. You can say "open browser" instead of <p style="margin: 0;">The AI understands context. You can say "open browser" instead of
remembering the exact application name - it will figure out what you mean.</p> remembering the exact application name - it will figure out what you mean.</p>
</div> </div>
<!-- Context Analysis -->
<h2 id="context" style="margin-top: 4rem;">Context Analysis</h2> <h2 id="context" style="margin-top: 4rem;">Context Analysis</h2>
<p> <p>
Press <kbd>Super</kbd> + <kbd>A</kbd> to see AI-powered analysis of your current workspace. Press <kbd>Super</kbd> + <kbd>A</kbd> to see AI-powered analysis of your current workspace.
</p> </p>
<div class="card"> <div class="card">
<h3>What It Shows</h3> <h3>What It Shows</h3>
<ul style="padding-left: 1.25rem;"> <ul style="padding-left: 1.25rem;">
@ -237,13 +220,10 @@ model = google/gemini-2.0-flash-exp:free</code></pre>
<li><strong>Workspace Summary</strong> - Overview of open applications</li> <li><strong>Workspace Summary</strong> - Overview of open applications</li>
</ul> </ul>
</div> </div>
<!-- Setup Exa -->
<h2 id="exa" style="margin-top: 4rem;">Setting Up Exa Search</h2> <h2 id="exa" style="margin-top: 4rem;">Setting Up Exa Search</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;"> <p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Exa provides semantic search - finding content based on meaning rather than exact keywords. Exa provides semantic search - finding content based on meaning rather than exact keywords.
</p> </p>
<div class="steps"> <div class="steps">
<div class="step"> <div class="step">
<div class="step-number">1</div> <div class="step-number">1</div>
@ -253,7 +233,6 @@ model = google/gemini-2.0-flash-exp:free</code></pre>
and create an account to get your API key.</p> and create an account to get your API key.</p>
</div> </div>
</div> </div>
<div class="step"> <div class="step">
<div class="step-number">2</div> <div class="step-number">2</div>
<div class="step-content"> <div class="step-content">
@ -265,7 +244,6 @@ model = google/gemini-2.0-flash-exp:free</code></pre>
<pre><code>export EXA_API_KEY="your-exa-key-here"</code></pre> <pre><code>export EXA_API_KEY="your-exa-key-here"</code></pre>
</div> </div>
</div> </div>
<div class="step"> <div class="step">
<div class="step-number">3</div> <div class="step-number">3</div>
<div class="step-content"> <div class="step-content">
@ -274,13 +252,10 @@ model = google/gemini-2.0-flash-exp:free</code></pre>
</div> </div>
</div> </div>
</div> </div>
<!-- Semantic Search -->
<h2 id="search" style="margin-top: 4rem;">Using Semantic Search</h2> <h2 id="search" style="margin-top: 4rem;">Using Semantic Search</h2>
<p> <p>
Unlike traditional search, Exa understands the meaning of your query. Unlike traditional search, Exa understands the meaning of your query.
</p> </p>
<div class="comparison" style="grid-template-columns: repeat(2, 1fr);"> <div class="comparison" style="grid-template-columns: repeat(2, 1fr);">
<div class="comparison-card"> <div class="comparison-card">
<h3>Traditional Search</h3> <h3>Traditional Search</h3>
@ -307,7 +282,6 @@ model = google/gemini-2.0-flash-exp:free</code></pre>
</p> </p>
</div> </div>
</div> </div>
<h3 style="margin-top: 2rem;">Search Tips</h3> <h3 style="margin-top: 2rem;">Search Tips</h3>
<ul> <ul>
<li>Use natural, conversational queries</li> <li>Use natural, conversational queries</li>
@ -315,14 +289,11 @@ model = google/gemini-2.0-flash-exp:free</code></pre>
<li>Results appear in a dmenu/rofi list - select to open in browser</li> <li>Results appear in a dmenu/rofi list - select to open in browser</li>
<li>Search includes articles, documentation, tutorials, and more</li> <li>Search includes articles, documentation, tutorials, and more</li>
</ul> </ul>
<!-- Privacy -->
<h2 id="privacy" style="margin-top: 4rem;">Privacy Considerations</h2> <h2 id="privacy" style="margin-top: 4rem;">Privacy Considerations</h2>
<div class="alert alert-warning"> <div class="alert alert-warning">
<strong class="alert-title">Data Sent to External Services</strong> <strong class="alert-title">Data Sent to External Services</strong>
<p style="margin: 0;">When using AI features, the following data is sent to external APIs:</p> <p style="margin: 0;">When using AI features, the following data is sent to external APIs:</p>
</div> </div>
<div class="table-wrapper" style="margin-top: 1rem;"> <div class="table-wrapper" style="margin-top: 1rem;">
<table> <table>
<thead> <thead>
@ -351,7 +322,6 @@ model = google/gemini-2.0-flash-exp:free</code></pre>
</tbody> </tbody>
</table> </table>
</div> </div>
<p style="margin-top: 1rem; color: var(--text-muted);"> <p style="margin-top: 1rem; color: var(--text-muted);">
If you're concerned about privacy, you can: If you're concerned about privacy, you can:
</p> </p>
@ -360,10 +330,7 @@ model = google/gemini-2.0-flash-exp:free</code></pre>
<li>Use OpenRouter with privacy-focused models</li> <li>Use OpenRouter with privacy-focused models</li>
<li>Only use AI features when needed</li> <li>Only use AI features when needed</li>
</ul> </ul>
<!-- Troubleshooting -->
<h2 id="troubleshooting" style="margin-top: 4rem;">Troubleshooting</h2> <h2 id="troubleshooting" style="margin-top: 4rem;">Troubleshooting</h2>
<div class="faq-item"> <div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)"> <button class="faq-question" onclick="toggleFaq(this)">
AI commands don't work - "API key not configured" AI commands don't work - "API key not configured"
@ -380,7 +347,6 @@ model = google/gemini-2.0-flash-exp:free</code></pre>
</div> </div>
</div> </div>
</div> </div>
<div class="faq-item"> <div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)"> <button class="faq-question" onclick="toggleFaq(this)">
Slow responses from AI Slow responses from AI
@ -392,7 +358,6 @@ model = google/gemini-2.0-flash-exp:free</code></pre>
</div> </div>
</div> </div>
</div> </div>
<div class="faq-item"> <div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)"> <button class="faq-question" onclick="toggleFaq(this)">
Exa search returns no results Exa search returns no results
@ -404,11 +369,9 @@ model = google/gemini-2.0-flash-exp:free</code></pre>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
</main> </main>
<footer> <footer>
<div class="container"> <div class="container">
<div class="footer-grid"> <div class="footer-grid">
@ -452,7 +415,6 @@ model = google/gemini-2.0-flash-exp:free</code></pre>
</div> </div>
</div> </div>
</footer> </footer>
<script src="js/main.js"></script> <script src="js/main.js"></script>
</body> </body>
</html> </html>

View File

@ -37,7 +37,6 @@
</div> </div>
</nav> </nav>
</header> </header>
<main> <main>
<section class="hero" style="padding: 8rem 0 4rem;"> <section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content"> <div class="container hero-content">
@ -47,7 +46,6 @@
</p> </p>
</div> </div>
</section> </section>
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<h2>Overview</h2> <h2>Overview</h2>
@ -56,7 +54,6 @@
A global <code>DWNState</code> singleton manages all state, and the main event loop A global <code>DWNState</code> singleton manages all state, and the main event loop
dispatches X11 events to specialized modules. dispatches X11 events to specialized modules.
</p> </p>
<div class="card" style="margin: 2rem 0;"> <div class="card" style="margin: 2rem 0;">
<h3>Project Statistics</h3> <h3>Project Statistics</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; text-align: center;"> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; text-align: center;">
@ -78,8 +75,6 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Directory Structure -->
<h2 id="structure" style="margin-top: 3rem;">Directory Structure</h2> <h2 id="structure" style="margin-top: 3rem;">Directory Structure</h2>
<div class="code-header"> <div class="code-header">
<span>Project Layout</span> <span>Project Layout</span>
@ -104,8 +99,6 @@
├── Makefile # Build system ├── Makefile # Build system
├── CLAUDE.md # AI assistant context ├── CLAUDE.md # AI assistant context
└── README.md # Project readme</code></pre> └── README.md # Project readme</code></pre>
<!-- Core Modules -->
<h2 id="modules" style="margin-top: 3rem;">Core Modules</h2> <h2 id="modules" style="margin-top: 3rem;">Core Modules</h2>
<div class="table-wrapper"> <div class="table-wrapper">
<table> <table>
@ -185,8 +178,6 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Module Dependencies -->
<h2 id="dependencies" style="margin-top: 3rem;">Module Dependencies</h2> <h2 id="dependencies" style="margin-top: 3rem;">Module Dependencies</h2>
<div class="card"> <div class="card">
<pre style="margin: 0; background: transparent; border: none; padding: 0;"><code>main.c (orchestrator) <pre style="margin: 0; background: transparent; border: none; padding: 0;"><code>main.c (orchestrator)
@ -208,14 +199,11 @@
└── keys.c └── keys.c
└── config.c</code></pre> └── config.c</code></pre>
</div> </div>
<!-- Global State -->
<h2 id="state" style="margin-top: 3rem;">Global State (DWNState)</h2> <h2 id="state" style="margin-top: 3rem;">Global State (DWNState)</h2>
<p> <p>
All window manager state is centralized in a single <code>DWNState</code> structure. All window manager state is centralized in a single <code>DWNState</code> structure.
This simplifies state management and makes the codebase easier to understand. This simplifies state management and makes the codebase easier to understand.
</p> </p>
<div class="code-header"> <div class="code-header">
<span>include/dwn.h (simplified)</span> <span>include/dwn.h (simplified)</span>
</div> </div>
@ -223,32 +211,23 @@
Display *display; // X11 connection Display *display; // X11 connection
Window root; // Root window Window root; // Root window
int screen; // Default screen int screen; // Default screen
Client *clients[MAX_CLIENTS]; // All managed windows Client *clients[MAX_CLIENTS]; // All managed windows
int client_count; int client_count;
Workspace workspaces[MAX_WORKSPACES]; // Virtual desktops Workspace workspaces[MAX_WORKSPACES]; // Virtual desktops
int current_workspace; int current_workspace;
Panel top_panel; Panel top_panel;
Panel bottom_panel; Panel bottom_panel;
Config config; // User configuration Config config; // User configuration
KeyBinding keys[MAX_KEYBINDINGS]; KeyBinding keys[MAX_KEYBINDINGS];
// EWMH atoms // EWMH atoms
Atom atoms[ATOM_COUNT]; Atom atoms[ATOM_COUNT];
} DWNState; } DWNState;
extern DWNState *dwn; // Global singleton</code></pre> extern DWNState *dwn; // Global singleton</code></pre>
<!-- Event Loop -->
<h2 id="events" style="margin-top: 3rem;">Event Loop</h2> <h2 id="events" style="margin-top: 3rem;">Event Loop</h2>
<p> <p>
DWN uses a traditional X11 event loop with XNextEvent. Events are dispatched DWN uses a traditional X11 event loop with XNextEvent. Events are dispatched
to appropriate handlers based on type. to appropriate handlers based on type.
</p> </p>
<div class="code-header"> <div class="code-header">
<span>main.c (simplified)</span> <span>main.c (simplified)</span>
</div> </div>
@ -256,11 +235,9 @@ extern DWNState *dwn; // Global singleton</code></pre>
dwn_init(); // Initialize X11, atoms, config dwn_init(); // Initialize X11, atoms, config
setup_keybindings(); // Register keyboard shortcuts setup_keybindings(); // Register keyboard shortcuts
setup_panels(); // Create panel windows setup_panels(); // Create panel windows
XEvent event; XEvent event;
while (running) { while (running) {
XNextEvent(dwn->display, &event); XNextEvent(dwn->display, &event);
switch (event.type) { switch (event.type) {
case MapRequest: case MapRequest:
handle_map_request(&event.xmaprequest); handle_map_request(&event.xmaprequest);
@ -280,12 +257,9 @@ extern DWNState *dwn; // Global singleton</code></pre>
// ... more event types // ... more event types
} }
} }
dwn_cleanup(); dwn_cleanup();
return 0; return 0;
}</code></pre> }</code></pre>
<!-- Key Constants -->
<h2 id="constants" style="margin-top: 3rem;">Key Constants</h2> <h2 id="constants" style="margin-top: 3rem;">Key Constants</h2>
<div class="table-wrapper"> <div class="table-wrapper">
<table> <table>
@ -325,8 +299,6 @@ extern DWNState *dwn; // Global singleton</code></pre>
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Coding Conventions -->
<h2 id="conventions" style="margin-top: 3rem;">Coding Conventions</h2> <h2 id="conventions" style="margin-top: 3rem;">Coding Conventions</h2>
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);"> <div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="card"> <div class="card">
@ -348,33 +320,26 @@ extern DWNState *dwn; // Global singleton</code></pre>
</ul> </ul>
</div> </div>
</div> </div>
<div class="code-header" style="margin-top: 1.5rem;"> <div class="code-header" style="margin-top: 1.5rem;">
<span>Example Function</span> <span>Example Function</span>
</div> </div>
<pre><code>void client_focus(Client *c) { <pre><code>void client_focus(Client *c) {
if (!c) return; if (!c) return;
// Unfocus previous // Unfocus previous
if (dwn->focused && dwn->focused != c) { if (dwn->focused && dwn->focused != c) {
client_unfocus(dwn->focused); client_unfocus(dwn->focused);
} }
dwn->focused = c; dwn->focused = c;
XSetInputFocus(dwn->display, c->window, RevertToPointerRoot, CurrentTime); XSetInputFocus(dwn->display, c->window, RevertToPointerRoot, CurrentTime);
XRaiseWindow(dwn->display, c->frame); XRaiseWindow(dwn->display, c->frame);
decorations_update(c); decorations_update(c);
atoms_set_active_window(c->window); atoms_set_active_window(c->window);
}</code></pre> }</code></pre>
<!-- EWMH/ICCCM -->
<h2 id="protocols" style="margin-top: 3rem;">EWMH/ICCCM Support</h2> <h2 id="protocols" style="margin-top: 3rem;">EWMH/ICCCM Support</h2>
<p> <p>
DWN implements key Extended Window Manager Hints and ICCCM protocols DWN implements key Extended Window Manager Hints and ICCCM protocols
for compatibility with modern applications. for compatibility with modern applications.
</p> </p>
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);"> <div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="card"> <div class="card">
<h3>EWMH Atoms</h3> <h3>EWMH Atoms</h3>
@ -407,13 +372,10 @@ extern DWNState *dwn; // Global singleton</code></pre>
</ul> </ul>
</div> </div>
</div> </div>
<!-- Build System -->
<h2 id="build" style="margin-top: 3rem;">Build System</h2> <h2 id="build" style="margin-top: 3rem;">Build System</h2>
<p> <p>
DWN uses a simple Makefile-based build system with pkg-config for dependency detection. DWN uses a simple Makefile-based build system with pkg-config for dependency detection.
</p> </p>
<div class="table-wrapper"> <div class="table-wrapper">
<table> <table>
<thead> <thead>
@ -467,13 +429,10 @@ extern DWNState *dwn; // Global singleton</code></pre>
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Contributing -->
<h2 id="contributing" style="margin-top: 3rem;">Contributing</h2> <h2 id="contributing" style="margin-top: 3rem;">Contributing</h2>
<p> <p>
Contributions are welcome! Here's how to get started: Contributions are welcome! Here's how to get started:
</p> </p>
<div class="steps"> <div class="steps">
<div class="step"> <div class="step">
<div class="step-number">1</div> <div class="step-number">1</div>
@ -511,11 +470,9 @@ extern DWNState *dwn; // Global singleton</code></pre>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
</main> </main>
<footer> <footer>
<div class="container"> <div class="container">
<div class="footer-grid"> <div class="footer-grid">
@ -559,7 +516,6 @@ extern DWNState *dwn; // Global singleton</code></pre>
</div> </div>
</div> </div>
</footer> </footer>
<script src="js/main.js"></script> <script src="js/main.js"></script>
</body> </body>
</html> </html>

View File

@ -37,7 +37,6 @@
</div> </div>
</nav> </nav>
</header> </header>
<main> <main>
<section class="hero" style="padding: 8rem 0 4rem;"> <section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content"> <div class="container hero-content">
@ -47,7 +46,6 @@
</p> </p>
</div> </div>
</section> </section>
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<h2>Configuration File</h2> <h2>Configuration File</h2>
@ -55,19 +53,15 @@
DWN reads its configuration from <code>~/.config/dwn/config</code> using an INI-style format. DWN reads its configuration from <code>~/.config/dwn/config</code> using an INI-style format.
Changes take effect on restart (or you can reload in a future version). Changes take effect on restart (or you can reload in a future version).
</p> </p>
<div class="alert alert-info"> <div class="alert alert-info">
<strong class="alert-title">First Run</strong> <strong class="alert-title">First Run</strong>
<p style="margin: 0;">DWN creates a default configuration file on first run if one doesn't exist. <p style="margin: 0;">DWN creates a default configuration file on first run if one doesn't exist.
You can also copy the example config from the source repository.</p> You can also copy the example config from the source repository.</p>
</div> </div>
<!-- General Section -->
<h2 id="general" style="margin-top: 3rem;">[general] - Core Settings</h2> <h2 id="general" style="margin-top: 3rem;">[general] - Core Settings</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;"> <p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Basic behavior settings for applications and focus handling. Basic behavior settings for applications and focus handling.
</p> </p>
<div class="table-wrapper"> <div class="table-wrapper">
<table> <table>
<thead> <thead>
@ -106,7 +100,6 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="code-header"> <div class="code-header">
<span>Example</span> <span>Example</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button> <button class="copy-btn" onclick="copyCode(this)">Copy</button>
@ -117,13 +110,10 @@ launcher = rofi -show run
file_manager = nautilus file_manager = nautilus
focus_mode = click focus_mode = click
decorations = true</code></pre> decorations = true</code></pre>
<!-- Appearance Section -->
<h2 id="appearance" style="margin-top: 3rem;">[appearance] - Visual Settings</h2> <h2 id="appearance" style="margin-top: 3rem;">[appearance] - Visual Settings</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;"> <p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Control the visual appearance of windows, panels, and gaps. Control the visual appearance of windows, panels, and gaps.
</p> </p>
<div class="table-wrapper"> <div class="table-wrapper">
<table> <table>
<thead> <thead>
@ -168,7 +158,6 @@ decorations = true</code></pre>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="code-header"> <div class="code-header">
<span>Example</span> <span>Example</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button> <button class="copy-btn" onclick="copyCode(this)">Copy</button>
@ -179,13 +168,10 @@ title_height = 28
panel_height = 32 panel_height = 32
gap = 8 gap = 8
font = DejaVu Sans-10</code></pre> font = DejaVu Sans-10</code></pre>
<!-- Layout Section -->
<h2 id="layout" style="margin-top: 3rem;">[layout] - Layout Behavior</h2> <h2 id="layout" style="margin-top: 3rem;">[layout] - Layout Behavior</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;"> <p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Configure the default layout mode and tiling parameters. Configure the default layout mode and tiling parameters.
</p> </p>
<div class="table-wrapper"> <div class="table-wrapper">
<table> <table>
<thead> <thead>
@ -218,7 +204,6 @@ font = DejaVu Sans-10</code></pre>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="code-header"> <div class="code-header">
<span>Example</span> <span>Example</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button> <button class="copy-btn" onclick="copyCode(this)">Copy</button>
@ -227,13 +212,10 @@ font = DejaVu Sans-10</code></pre>
default = tiling default = tiling
master_ratio = 0.60 master_ratio = 0.60
master_count = 1</code></pre> master_count = 1</code></pre>
<!-- Panels Section -->
<h2 id="panels" style="margin-top: 3rem;">[panels] - Panel Visibility</h2> <h2 id="panels" style="margin-top: 3rem;">[panels] - Panel Visibility</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;"> <p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Control which panels are displayed. Control which panels are displayed.
</p> </p>
<div class="table-wrapper"> <div class="table-wrapper">
<table> <table>
<thead> <thead>
@ -257,7 +239,6 @@ master_count = 1</code></pre>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="code-header"> <div class="code-header">
<span>Example - Minimal Setup</span> <span>Example - Minimal Setup</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button> <button class="copy-btn" onclick="copyCode(this)">Copy</button>
@ -265,13 +246,10 @@ master_count = 1</code></pre>
<pre><code>[panels] <pre><code>[panels]
top = true top = true
bottom = false</code></pre> bottom = false</code></pre>
<!-- Colors Section -->
<h2 id="colors" style="margin-top: 3rem;">[colors] - Color Scheme</h2> <h2 id="colors" style="margin-top: 3rem;">[colors] - Color Scheme</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;"> <p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Customize all colors using hex format (#RRGGBB). Customize all colors using hex format (#RRGGBB).
</p> </p>
<div class="table-wrapper"> <div class="table-wrapper">
<table> <table>
<thead> <thead>
@ -350,7 +328,6 @@ bottom = false</code></pre>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="code-header"> <div class="code-header">
<span>Example - Nord Theme</span> <span>Example - Nord Theme</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button> <button class="copy-btn" onclick="copyCode(this)">Copy</button>
@ -369,13 +346,10 @@ border_focused = #88c0d0
border_unfocused = #3b4252 border_unfocused = #3b4252
notification_bg = #3b4252 notification_bg = #3b4252
notification_fg = #eceff4</code></pre> notification_fg = #eceff4</code></pre>
<!-- AI Section -->
<h2 id="ai" style="margin-top: 3rem;">[ai] - AI Integration</h2> <h2 id="ai" style="margin-top: 3rem;">[ai] - AI Integration</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;"> <p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Configure AI features. See <a href="ai-features.html">AI Features</a> for full setup instructions. Configure AI features. See <a href="ai-features.html">AI Features</a> for full setup instructions.
</p> </p>
<div class="table-wrapper"> <div class="table-wrapper">
<table> <table>
<thead> <thead>
@ -400,7 +374,6 @@ notification_fg = #eceff4</code></pre>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="code-header"> <div class="code-header">
<span>Example</span> <span>Example</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button> <button class="copy-btn" onclick="copyCode(this)">Copy</button>
@ -409,14 +382,11 @@ notification_fg = #eceff4</code></pre>
model = google/gemini-2.0-flash-exp:free model = google/gemini-2.0-flash-exp:free
openrouter_api_key = sk-or-v1-your-key-here openrouter_api_key = sk-or-v1-your-key-here
exa_api_key = your-exa-key-here</code></pre> exa_api_key = your-exa-key-here</code></pre>
<div class="alert alert-warning" style="margin-top: 1rem;"> <div class="alert alert-warning" style="margin-top: 1rem;">
<strong class="alert-title">Security Note</strong> <strong class="alert-title">Security Note</strong>
<p style="margin: 0;">For better security, use environment variables instead of storing API keys in the config file: <p style="margin: 0;">For better security, use environment variables instead of storing API keys in the config file:
<code>export OPENROUTER_API_KEY=sk-or-v1-...</code></p> <code>export OPENROUTER_API_KEY=sk-or-v1-...</code></p>
</div> </div>
<!-- Complete Example -->
<h2 style="margin-top: 3rem;">Complete Configuration Example</h2> <h2 style="margin-top: 3rem;">Complete Configuration Example</h2>
<div class="code-header"> <div class="code-header">
<span>~/.config/dwn/config</span> <span>~/.config/dwn/config</span>
@ -424,30 +394,25 @@ exa_api_key = your-exa-key-here</code></pre>
</div> </div>
<pre><code># DWN Window Manager Configuration <pre><code># DWN Window Manager Configuration
# https://dwn.github.io # https://dwn.github.io
[general] [general]
terminal = alacritty terminal = alacritty
launcher = rofi -show drun launcher = rofi -show drun
file_manager = thunar file_manager = thunar
focus_mode = click focus_mode = click
decorations = true decorations = true
[appearance] [appearance]
border_width = 2 border_width = 2
title_height = 24 title_height = 24
panel_height = 28 panel_height = 28
gap = 6 gap = 6
font = DejaVu Sans-10 font = DejaVu Sans-10
[layout] [layout]
default = tiling default = tiling
master_ratio = 0.55 master_ratio = 0.55
master_count = 1 master_count = 1
[panels] [panels]
top = true top = true
bottom = true bottom = true
[colors] [colors]
panel_bg = #1a1a2e panel_bg = #1a1a2e
panel_fg = #e0e0e0 panel_fg = #e0e0e0
@ -462,15 +427,12 @@ border_focused = #4a90d9
border_unfocused = #333333 border_unfocused = #333333
notification_bg = #2a2a3e notification_bg = #2a2a3e
notification_fg = #ffffff notification_fg = #ffffff
[ai] [ai]
model = google/gemini-2.0-flash-exp:free model = google/gemini-2.0-flash-exp:free
# API keys via environment variables recommended</code></pre> # API keys via environment variables recommended</code></pre>
</div> </div>
</section> </section>
</main> </main>
<footer> <footer>
<div class="container"> <div class="container">
<div class="footer-grid"> <div class="footer-grid">
@ -514,7 +476,6 @@ model = google/gemini-2.0-flash-exp:free
</div> </div>
</div> </div>
</footer> </footer>
<script src="js/main.js"></script> <script src="js/main.js"></script>
</body> </body>
</html> </html>

View File

@ -37,7 +37,6 @@
</div> </div>
</nav> </nav>
</header> </header>
<div class="docs-layout container"> <div class="docs-layout container">
<aside class="docs-sidebar"> <aside class="docs-sidebar">
<ul> <ul>
@ -70,23 +69,19 @@
</li> </li>
</ul> </ul>
</aside> </aside>
<main class="docs-content"> <main class="docs-content">
<h1 id="introduction">Getting Started with DWN</h1> <h1 id="introduction">Getting Started with DWN</h1>
<p class="lead"> <p class="lead">
Learn the fundamentals of DWN and become productive in minutes. Learn the fundamentals of DWN and become productive in minutes.
</p> </p>
<h2 id="first-steps">First Steps</h2> <h2 id="first-steps">First Steps</h2>
<p> <p>
After <a href="installation.html">installing DWN</a> and starting your session, After <a href="installation.html">installing DWN</a> and starting your session,
you'll see a clean desktop with two panels: a top panel with workspace indicators, you'll see a clean desktop with two panels: a top panel with workspace indicators,
taskbar, and system tray, and a bottom panel showing the clock. taskbar, and system tray, and a bottom panel showing the clock.
</p> </p>
<h3>Opening Your First Application</h3> <h3>Opening Your First Application</h3>
<p>Start by launching a terminal and application launcher:</p> <p>Start by launching a terminal and application launcher:</p>
<div class="table-wrapper"> <div class="table-wrapper">
<table> <table>
<thead> <thead>
@ -115,28 +110,23 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="alert alert-info"> <div class="alert alert-info">
<strong class="alert-title">Tip: Run the Tutorial</strong> <strong class="alert-title">Tip: Run the Tutorial</strong>
<p style="margin: 0;">Press <kbd>Super</kbd> + <kbd>T</kbd> to start an interactive <p style="margin: 0;">Press <kbd>Super</kbd> + <kbd>T</kbd> to start an interactive
tutorial that will guide you through all essential shortcuts.</p> tutorial that will guide you through all essential shortcuts.</p>
</div> </div>
<h2 id="basic-concepts">Basic Concepts</h2> <h2 id="basic-concepts">Basic Concepts</h2>
<h3>The Super Key</h3> <h3>The Super Key</h3>
<p> <p>
Most DWN shortcuts use the <kbd>Super</kbd> key (often the Windows key or Command key). Most DWN shortcuts use the <kbd>Super</kbd> key (often the Windows key or Command key).
This keeps shortcuts separate from application shortcuts that typically use This keeps shortcuts separate from application shortcuts that typically use
<kbd>Ctrl</kbd> or <kbd>Alt</kbd>. <kbd>Ctrl</kbd> or <kbd>Alt</kbd>.
</p> </p>
<h3>Focus Model</h3> <h3>Focus Model</h3>
<p> <p>
By default, DWN uses "click to focus" - you click on a window to focus it. By default, DWN uses "click to focus" - you click on a window to focus it.
You can change this to "focus follows mouse" (sloppy focus) in the configuration. You can change this to "focus follows mouse" (sloppy focus) in the configuration.
</p> </p>
<h3>Window Decorations</h3> <h3>Window Decorations</h3>
<p> <p>
Each window has a title bar showing its name. The title bar color indicates focus: Each window has a title bar showing its name. The title bar color indicates focus:
@ -145,7 +135,6 @@
<li><strong>Blue title bar</strong> - Focused window</li> <li><strong>Blue title bar</strong> - Focused window</li>
<li><strong>Gray title bar</strong> - Unfocused window</li> <li><strong>Gray title bar</strong> - Unfocused window</li>
</ul> </ul>
<h2 id="tutorial">Interactive Tutorial</h2> <h2 id="tutorial">Interactive Tutorial</h2>
<p> <p>
DWN includes a built-in interactive tutorial that teaches you essential shortcuts DWN includes a built-in interactive tutorial that teaches you essential shortcuts
@ -157,14 +146,11 @@
<li>Automatically advances when you complete each step</li> <li>Automatically advances when you complete each step</li>
<li>Covers all essential shortcuts from basic to advanced</li> <li>Covers all essential shortcuts from basic to advanced</li>
</ul> </ul>
<div class="card"> <div class="card">
<h3>Start the Tutorial</h3> <h3>Start the Tutorial</h3>
<p>Press <kbd>Super</kbd> + <kbd>T</kbd> at any time to start or resume the tutorial.</p> <p>Press <kbd>Super</kbd> + <kbd>T</kbd> at any time to start or resume the tutorial.</p>
</div> </div>
<h2 id="windows">Managing Windows</h2> <h2 id="windows">Managing Windows</h2>
<h3>Window Operations</h3> <h3>Window Operations</h3>
<div class="table-wrapper"> <div class="table-wrapper">
<table> <table>
@ -202,20 +188,17 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<h3>Moving and Resizing</h3> <h3>Moving and Resizing</h3>
<p>In floating mode, you can move and resize windows with the mouse:</p> <p>In floating mode, you can move and resize windows with the mouse:</p>
<ul> <ul>
<li><strong>Move</strong> - Click and drag the title bar</li> <li><strong>Move</strong> - Click and drag the title bar</li>
<li><strong>Resize</strong> - Drag any window edge or corner</li> <li><strong>Resize</strong> - Drag any window edge or corner</li>
</ul> </ul>
<h2 id="workspaces">Using Workspaces</h2> <h2 id="workspaces">Using Workspaces</h2>
<p> <p>
DWN provides 9 virtual workspaces to organize your windows. You can see which DWN provides 9 virtual workspaces to organize your windows. You can see which
workspaces are active in the top panel. workspaces are active in the top panel.
</p> </p>
<h3>Workspace Navigation</h3> <h3>Workspace Navigation</h3>
<div class="table-wrapper"> <div class="table-wrapper">
<table> <table>
@ -245,7 +228,6 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<h3>Workspace Organization Tips</h3> <h3>Workspace Organization Tips</h3>
<ul> <ul>
<li><strong>Workspace 1</strong> - Main work (editor, terminal)</li> <li><strong>Workspace 1</strong> - Main work (editor, terminal)</li>
@ -253,13 +235,11 @@
<li><strong>Workspace 3</strong> - Communication (email, chat)</li> <li><strong>Workspace 3</strong> - Communication (email, chat)</li>
<li><strong>Workspace 4-9</strong> - Project-specific contexts</li> <li><strong>Workspace 4-9</strong> - Project-specific contexts</li>
</ul> </ul>
<h2 id="layouts">Layout Modes</h2> <h2 id="layouts">Layout Modes</h2>
<p> <p>
DWN supports three layout modes. Press <kbd>Super</kbd> + <kbd>Space</kbd> to cycle DWN supports three layout modes. Press <kbd>Super</kbd> + <kbd>Space</kbd> to cycle
through them. through them.
</p> </p>
<div class="features-grid" style="grid-template-columns: repeat(3, 1fr); gap: 1rem; margin: 1.5rem 0;"> <div class="features-grid" style="grid-template-columns: repeat(3, 1fr); gap: 1rem; margin: 1.5rem 0;">
<div class="card" style="padding: 1.5rem;"> <div class="card" style="padding: 1.5rem;">
<h4>Tiling</h4> <h4>Tiling</h4>
@ -283,7 +263,6 @@
</p> </p>
</div> </div>
</div> </div>
<h3>Tiling Layout Controls</h3> <h3>Tiling Layout Controls</h3>
<div class="table-wrapper"> <div class="table-wrapper">
<table> <table>
@ -313,9 +292,7 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<h2 id="panels">Panels & System Tray</h2> <h2 id="panels">Panels & System Tray</h2>
<h3>Top Panel</h3> <h3>Top Panel</h3>
<p>The top panel contains:</p> <p>The top panel contains:</p>
<ul> <ul>
@ -323,7 +300,6 @@
<li><strong>Taskbar</strong> - Shows windows on current workspace</li> <li><strong>Taskbar</strong> - Shows windows on current workspace</li>
<li><strong>System tray</strong> - Battery, volume, WiFi (see below)</li> <li><strong>System tray</strong> - Battery, volume, WiFi (see below)</li>
</ul> </ul>
<h3>System Tray</h3> <h3>System Tray</h3>
<div class="table-wrapper"> <div class="table-wrapper">
<table> <table>
@ -357,7 +333,6 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<h3>Bottom Panel</h3> <h3>Bottom Panel</h3>
<p> <p>
The bottom panel shows the current time and a scrolling news ticker. Navigate The bottom panel shows the current time and a scrolling news ticker. Navigate
@ -365,7 +340,6 @@
the current article with <kbd>Super</kbd> + <kbd>Return</kbd>. Both panels can the current article with <kbd>Super</kbd> + <kbd>Return</kbd>. Both panels can
be hidden in the configuration if you prefer a minimal setup. be hidden in the configuration if you prefer a minimal setup.
</p> </p>
<h2>Next Steps</h2> <h2>Next Steps</h2>
<p>Now that you know the basics, explore these topics:</p> <p>Now that you know the basics, explore these topics:</p>
<ul> <ul>
@ -375,7 +349,6 @@
</ul> </ul>
</main> </main>
</div> </div>
<footer> <footer>
<div class="container"> <div class="container">
<div class="footer-grid"> <div class="footer-grid">
@ -419,7 +392,6 @@
</div> </div>
</div> </div>
</footer> </footer>
<script src="js/main.js"></script> <script src="js/main.js"></script>
</body> </body>
</html> </html>

View File

@ -37,7 +37,6 @@
</div> </div>
</nav> </nav>
</header> </header>
<main> <main>
<section class="hero" style="padding: 8rem 0 4rem;"> <section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content"> <div class="container hero-content">
@ -47,8 +46,6 @@
</p> </p>
</div> </div>
</section> </section>
<!-- Window Management -->
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<h2>Window Management</h2> <h2>Window Management</h2>
@ -56,7 +53,6 @@
DWN provides flexible window management that adapts to your workflow, whether you prefer DWN provides flexible window management that adapts to your workflow, whether you prefer
the precision of tiling or the freedom of floating windows. the precision of tiling or the freedom of floating windows.
</p> </p>
<div class="features-grid"> <div class="features-grid">
<div class="card"> <div class="card">
<h3><span class="card-icon">&#9783;</span> Tiling Layout</h3> <h3><span class="card-icon">&#9783;</span> Tiling Layout</h3>
@ -92,7 +88,6 @@
</ul> </ul>
</div> </div>
</div> </div>
<div class="alert alert-info" style="margin-top: 2rem;"> <div class="alert alert-info" style="margin-top: 2rem;">
<strong class="alert-title">Pro Tip</strong> <strong class="alert-title">Pro Tip</strong>
<p style="margin: 0;">Switch layouts instantly with <kbd>Super</kbd> + <kbd>Space</kbd>. <p style="margin: 0;">Switch layouts instantly with <kbd>Super</kbd> + <kbd>Space</kbd>.
@ -100,8 +95,6 @@
</div> </div>
</div> </div>
</section> </section>
<!-- Workspaces -->
<section class="section section-alt"> <section class="section section-alt">
<div class="container"> <div class="container">
<h2>Virtual Workspaces</h2> <h2>Virtual Workspaces</h2>
@ -109,7 +102,6 @@
Nine virtual desktops give you unlimited room to organize your work. Nine virtual desktops give you unlimited room to organize your work.
Each workspace maintains its own window state and layout preferences. Each workspace maintains its own window state and layout preferences.
</p> </p>
<div class="features-grid" style="grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));"> <div class="features-grid" style="grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));">
<div class="feature-card"> <div class="feature-card">
<div class="feature-icon">1-9</div> <div class="feature-icon">1-9</div>
@ -135,8 +127,6 @@
</div> </div>
</div> </div>
</section> </section>
<!-- Panel & System Tray -->
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<h2>Panels & System Tray</h2> <h2>Panels & System Tray</h2>
@ -144,7 +134,6 @@
Built-in panels provide essential information and quick access to common functions Built-in panels provide essential information and quick access to common functions
without needing external tools or status bars. without needing external tools or status bars.
</p> </p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;"> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
<div class="card"> <div class="card">
<h3>Top Panel</h3> <h3>Top Panel</h3>
@ -165,7 +154,6 @@
</ul> </ul>
</div> </div>
</div> </div>
<h3 style="margin-top: 3rem; margin-bottom: 1.5rem;">News Ticker</h3> <h3 style="margin-top: 3rem; margin-bottom: 1.5rem;">News Ticker</h3>
<div class="card"> <div class="card">
<p>The bottom panel includes a scrolling news ticker that displays headlines from a news feed. <p>The bottom panel includes a scrolling news ticker that displays headlines from a news feed.
@ -180,7 +168,6 @@
at 80 pixels per second keeps you informed without distraction. at 80 pixels per second keeps you informed without distraction.
</p> </p>
</div> </div>
<h3 style="margin-top: 3rem; margin-bottom: 1.5rem;">System Tray Features</h3> <h3 style="margin-top: 3rem; margin-bottom: 1.5rem;">System Tray Features</h3>
<div class="features-grid"> <div class="features-grid">
<div class="card"> <div class="card">
@ -213,8 +200,6 @@
</div> </div>
</div> </div>
</section> </section>
<!-- Notifications -->
<section class="section section-alt"> <section class="section section-alt">
<div class="container"> <div class="container">
<h2>Notification System</h2> <h2>Notification System</h2>
@ -222,7 +207,6 @@
Built-in D-Bus notification daemon following freedesktop.org standards. Built-in D-Bus notification daemon following freedesktop.org standards.
No need for external notification tools like dunst or notify-osd. No need for external notification tools like dunst or notify-osd.
</p> </p>
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);"> <div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="card"> <div class="card">
<h3>Standards Compliant</h3> <h3>Standards Compliant</h3>
@ -235,7 +219,6 @@
Notifications match your overall color scheme automatically.</p> Notifications match your overall color scheme automatically.</p>
</div> </div>
</div> </div>
<div class="alert alert-success" style="margin-top: 2rem;"> <div class="alert alert-success" style="margin-top: 2rem;">
<strong class="alert-title">Capacity</strong> <strong class="alert-title">Capacity</strong>
<p style="margin: 0;">DWN can display up to 32 notifications simultaneously, <p style="margin: 0;">DWN can display up to 32 notifications simultaneously,
@ -243,8 +226,6 @@
</div> </div>
</div> </div>
</section> </section>
<!-- AI Features Preview -->
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<h2>AI Integration</h2> <h2>AI Integration</h2>
@ -252,7 +233,6 @@
Optional AI features powered by OpenRouter API and Exa semantic search. Optional AI features powered by OpenRouter API and Exa semantic search.
Control your desktop with natural language and get intelligent assistance. Control your desktop with natural language and get intelligent assistance.
</p> </p>
<div class="features-grid"> <div class="features-grid">
<div class="card"> <div class="card">
<h3>&#129302; AI Command Palette</h3> <h3>&#129302; AI Command Palette</h3>
@ -275,15 +255,12 @@
</div> </div>
</div> </div>
</section> </section>
<!-- EWMH/ICCCM -->
<section class="section section-alt"> <section class="section section-alt">
<div class="container"> <div class="container">
<h2>Standards Compliance</h2> <h2>Standards Compliance</h2>
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;"> <p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
DWN implements EWMH and ICCCM protocols for maximum compatibility with X11 applications. DWN implements EWMH and ICCCM protocols for maximum compatibility with X11 applications.
</p> </p>
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);"> <div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="card"> <div class="card">
<h3>EWMH Support</h3> <h3>EWMH Support</h3>
@ -310,8 +287,6 @@
</div> </div>
</div> </div>
</section> </section>
<!-- Technical Specs -->
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<h2>Technical Specifications</h2> <h2>Technical Specifications</h2>
@ -361,8 +336,6 @@
</div> </div>
</div> </div>
</section> </section>
<!-- CTA -->
<section class="section section-alt"> <section class="section section-alt">
<div class="container" style="text-align: center;"> <div class="container" style="text-align: center;">
<h2>Ready to Try DWN?</h2> <h2>Ready to Try DWN?</h2>
@ -376,7 +349,6 @@
</div> </div>
</section> </section>
</main> </main>
<footer> <footer>
<div class="container"> <div class="container">
<div class="footer-grid"> <div class="footer-grid">
@ -420,7 +392,6 @@
</div> </div>
</div> </div>
</footer> </footer>
<script src="js/main.js"></script> <script src="js/main.js"></script>
</body> </body>
</html> </html>

View File

@ -38,9 +38,7 @@
</div> </div>
</nav> </nav>
</header> </header>
<main> <main>
<!-- Hero Section -->
<section class="hero"> <section class="hero">
<div class="container hero-content"> <div class="container hero-content">
<h1>Modern Window Management for X11</h1> <h1>Modern Window Management for X11</h1>
@ -54,8 +52,6 @@
</div> </div>
</div> </div>
</section> </section>
<!-- Key Features Overview -->
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<h2 style="text-align: center; margin-bottom: 1rem;">Why Choose DWN?</h2> <h2 style="text-align: center; margin-bottom: 1rem;">Why Choose DWN?</h2>
@ -103,8 +99,6 @@
</div> </div>
</div> </div>
</section> </section>
<!-- Stats -->
<section class="section section-alt"> <section class="section section-alt">
<div class="container"> <div class="container">
<div class="stats"> <div class="stats">
@ -127,8 +121,6 @@
</div> </div>
</div> </div>
</section> </section>
<!-- Quick Start -->
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<h2 style="text-align: center;">Get Up and Running in Minutes</h2> <h2 style="text-align: center;">Get Up and Running in Minutes</h2>
@ -136,7 +128,6 @@
DWN is designed for easy installation and immediate productivity. DWN is designed for easy installation and immediate productivity.
Build from source or use your distribution's package manager. Build from source or use your distribution's package manager.
</p> </p>
<div class="card" style="max-width: 700px; margin: 0 auto;"> <div class="card" style="max-width: 700px; margin: 0 auto;">
<div class="code-header"> <div class="code-header">
<span>Terminal</span> <span>Terminal</span>
@ -145,25 +136,19 @@
<pre><code># Clone the repository <pre><code># Clone the repository
git clone https://retoor.molodetz.nl/retoor/dwn.git git clone https://retoor.molodetz.nl/retoor/dwn.git
cd dwn cd dwn
# Install dependencies (auto-detects your distro) # Install dependencies (auto-detects your distro)
make deps make deps
# Build and install # Build and install
make make
sudo make install sudo make install
# Add to your .xinitrc # Add to your .xinitrc
echo "exec dwn" >> ~/.xinitrc</code></pre> echo "exec dwn" >> ~/.xinitrc</code></pre>
</div> </div>
<p style="text-align: center; margin-top: 2rem;"> <p style="text-align: center; margin-top: 2rem;">
<a href="installation.html" class="btn btn-primary">Full Installation Guide</a> <a href="installation.html" class="btn btn-primary">Full Installation Guide</a>
</p> </p>
</div> </div>
</section> </section>
<!-- Screenshots Preview -->
<section class="section section-alt"> <section class="section section-alt">
<div class="container"> <div class="container">
<h2 style="text-align: center;">See DWN in Action</h2> <h2 style="text-align: center;">See DWN in Action</h2>
@ -192,8 +177,6 @@ echo "exec dwn" >> ~/.xinitrc</code></pre>
</div> </div>
</div> </div>
</section> </section>
<!-- Comparison -->
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<h2 style="text-align: center;">How DWN Compares</h2> <h2 style="text-align: center;">How DWN Compares</h2>
@ -239,62 +222,9 @@ echo "exec dwn" >> ~/.xinitrc</code></pre>
</div> </div>
</div> </div>
</section> </section>
<!-- Testimonials -->
<section class="section section-alt">
<div class="container">
<h2 style="text-align: center;">What Users Say</h2>
<div class="testimonials">
<div class="testimonial">
<p class="testimonial-text">
"Finally, a window manager that doesn't make me choose between
productivity and usability. DWN just works."
</p>
<div class="testimonial-author">
<div class="testimonial-avatar">JD</div>
<div class="testimonial-info">
<strong>John Developer</strong>
<span>Senior Software Engineer</span>
</div>
</div>
</div>
<div class="testimonial">
<p class="testimonial-text">
"The AI command palette changed how I interact with my desktop.
It's like having an assistant for window management."
</p>
<div class="testimonial-author">
<div class="testimonial-avatar">SA</div>
<div class="testimonial-info">
<strong>Sarah Admin</strong>
<span>System Administrator</span>
</div>
</div>
</div>
<div class="testimonial">
<p class="testimonial-text">
"Coming from i3, the learning curve was minimal. The built-in
tutorial had me productive in under 10 minutes."
</p>
<div class="testimonial-author">
<div class="testimonial-avatar">ML</div>
<div class="testimonial-info">
<strong>Mike Linux</strong>
<span>DevOps Engineer</span>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- CTA Section -->
<section class="section"> <section class="section">
<div class="container" style="text-align: center;"> <div class="container" style="text-align: center;">
<h2>Ready to Transform Your Desktop?</h2> <h2>Ready to Try DWN?</h2>
<p style="color: var(--text-muted); max-width: 500px; margin: 0 auto 2rem;">
Join thousands of developers and power users who have made DWN their window manager of choice.
</p>
<div class="hero-buttons" style="justify-content: center;"> <div class="hero-buttons" style="justify-content: center;">
<a href="installation.html" class="btn btn-primary btn-lg">Install DWN</a> <a href="installation.html" class="btn btn-primary btn-lg">Install DWN</a>
<a href="documentation.html" class="btn btn-secondary btn-lg">Read the Docs</a> <a href="documentation.html" class="btn btn-secondary btn-lg">Read the Docs</a>
@ -302,7 +232,6 @@ echo "exec dwn" >> ~/.xinitrc</code></pre>
</div> </div>
</section> </section>
</main> </main>
<footer> <footer>
<div class="container"> <div class="container">
<div class="footer-grid"> <div class="footer-grid">
@ -346,7 +275,6 @@ echo "exec dwn" >> ~/.xinitrc</code></pre>
</div> </div>
</div> </div>
</footer> </footer>
<script src="js/main.js"></script> <script src="js/main.js"></script>
</body> </body>
</html> </html>

View File

@ -37,7 +37,6 @@
</div> </div>
</nav> </nav>
</header> </header>
<main> <main>
<section class="hero" style="padding: 8rem 0 4rem;"> <section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content"> <div class="container hero-content">
@ -47,15 +46,12 @@
</p> </p>
</div> </div>
</section> </section>
<!-- Requirements -->
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<h2>Requirements</h2> <h2>Requirements</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;"> <p style="color: var(--text-muted); margin-bottom: 2rem;">
DWN requires X11 and a few common libraries. Most Linux distributions include these by default. DWN requires X11 and a few common libraries. Most Linux distributions include these by default.
</p> </p>
<div class="card"> <div class="card">
<h3>Required Dependencies</h3> <h3>Required Dependencies</h3>
<div class="table-wrapper" style="margin-top: 1rem;"> <div class="table-wrapper" style="margin-top: 1rem;">
@ -114,15 +110,12 @@
</div> </div>
</div> </div>
</section> </section>
<!-- Quick Install -->
<section class="section section-alt"> <section class="section section-alt">
<div class="container"> <div class="container">
<h2>Quick Installation</h2> <h2>Quick Installation</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;"> <p style="color: var(--text-muted); margin-bottom: 2rem;">
The fastest way to get started. Our build system auto-detects your distribution. The fastest way to get started. Our build system auto-detects your distribution.
</p> </p>
<div class="steps"> <div class="steps">
<div class="step"> <div class="step">
<div class="step-number">1</div> <div class="step-number">1</div>
@ -137,7 +130,6 @@
cd dwn</code></pre> cd dwn</code></pre>
</div> </div>
</div> </div>
<div class="step"> <div class="step">
<div class="step-number">2</div> <div class="step-number">2</div>
<div class="step-content"> <div class="step-content">
@ -153,7 +145,6 @@ cd dwn</code></pre>
</p> </p>
</div> </div>
</div> </div>
<div class="step"> <div class="step">
<div class="step-number">3</div> <div class="step-number">3</div>
<div class="step-content"> <div class="step-content">
@ -166,7 +157,6 @@ cd dwn</code></pre>
<pre><code>make</code></pre> <pre><code>make</code></pre>
</div> </div>
</div> </div>
<div class="step"> <div class="step">
<div class="step-number">4</div> <div class="step-number">4</div>
<div class="step-content"> <div class="step-content">
@ -182,7 +172,6 @@ cd dwn</code></pre>
</p> </p>
</div> </div>
</div> </div>
<div class="step"> <div class="step">
<div class="step-number">5</div> <div class="step-number">5</div>
<div class="step-content"> <div class="step-content">
@ -198,19 +187,15 @@ cd dwn</code></pre>
</div> </div>
</div> </div>
</section> </section>
<!-- Distribution-specific -->
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<h2>Distribution-Specific Instructions</h2> <h2>Distribution-Specific Instructions</h2>
<div class="tabs"> <div class="tabs">
<button class="tab active" onclick="showTab('debian')">Debian/Ubuntu</button> <button class="tab active" onclick="showTab('debian')">Debian/Ubuntu</button>
<button class="tab" onclick="showTab('fedora')">Fedora</button> <button class="tab" onclick="showTab('fedora')">Fedora</button>
<button class="tab" onclick="showTab('arch')">Arch Linux</button> <button class="tab" onclick="showTab('arch')">Arch Linux</button>
<button class="tab" onclick="showTab('void')">Void Linux</button> <button class="tab" onclick="showTab('void')">Void Linux</button>
</div> </div>
<div id="debian" class="tab-content active"> <div id="debian" class="tab-content active">
<h3>Debian / Ubuntu / Linux Mint</h3> <h3>Debian / Ubuntu / Linux Mint</h3>
<div class="code-header"> <div class="code-header">
@ -230,14 +215,12 @@ sudo apt install -y \
libdbus-1-dev \ libdbus-1-dev \
libcurl4-openssl-dev \ libcurl4-openssl-dev \
pkg-config pkg-config
# Build and install # Build and install
git clone https://retoor.molodetz.nl/retoor/dwn.git git clone https://retoor.molodetz.nl/retoor/dwn.git
cd dwn cd dwn
make make
sudo make install</code></pre> sudo make install</code></pre>
</div> </div>
<div id="fedora" class="tab-content"> <div id="fedora" class="tab-content">
<h3>Fedora / RHEL / CentOS</h3> <h3>Fedora / RHEL / CentOS</h3>
<div class="code-header"> <div class="code-header">
@ -257,14 +240,12 @@ sudo dnf install -y \
dbus-devel \ dbus-devel \
libcurl-devel \ libcurl-devel \
pkg-config pkg-config
# Build and install # Build and install
git clone https://retoor.molodetz.nl/retoor/dwn.git git clone https://retoor.molodetz.nl/retoor/dwn.git
cd dwn cd dwn
make make
sudo make install</code></pre> sudo make install</code></pre>
</div> </div>
<div id="arch" class="tab-content"> <div id="arch" class="tab-content">
<h3>Arch Linux / Manjaro</h3> <h3>Arch Linux / Manjaro</h3>
<div class="code-header"> <div class="code-header">
@ -283,7 +264,6 @@ sudo pacman -S --needed \
dbus \ dbus \
curl \ curl \
pkg-config pkg-config
# Build and install # Build and install
git clone https://retoor.molodetz.nl/retoor/dwn.git git clone https://retoor.molodetz.nl/retoor/dwn.git
cd dwn cd dwn
@ -295,7 +275,6 @@ sudo make install</code></pre>
<code>yay -S dwn-git</code></p> <code>yay -S dwn-git</code></p>
</div> </div>
</div> </div>
<div id="void" class="tab-content"> <div id="void" class="tab-content">
<h3>Void Linux</h3> <h3>Void Linux</h3>
<div class="code-header"> <div class="code-header">
@ -314,7 +293,6 @@ sudo xbps-install -S \
dbus-devel \ dbus-devel \
libcurl-devel \ libcurl-devel \
pkg-config pkg-config
# Build and install # Build and install
git clone https://retoor.molodetz.nl/retoor/dwn.git git clone https://retoor.molodetz.nl/retoor/dwn.git
cd dwn cd dwn
@ -323,15 +301,12 @@ sudo make install</code></pre>
</div> </div>
</div> </div>
</section> </section>
<!-- Session Setup -->
<section class="section section-alt"> <section class="section section-alt">
<div class="container"> <div class="container">
<h2>Session Setup</h2> <h2>Session Setup</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;"> <p style="color: var(--text-muted); margin-bottom: 2rem;">
Configure your display manager or xinit to start DWN. Configure your display manager or xinit to start DWN.
</p> </p>
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);"> <div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="card"> <div class="card">
<h3>Using xinit / startx</h3> <h3>Using xinit / startx</h3>
@ -342,10 +317,8 @@ sudo make install</code></pre>
</div> </div>
<pre><code># Optional: set display settings <pre><code># Optional: set display settings
xrandr --output DP-1 --mode 2560x1440 xrandr --output DP-1 --mode 2560x1440
# Optional: set wallpaper # Optional: set wallpaper
feh --bg-fill ~/wallpaper.jpg feh --bg-fill ~/wallpaper.jpg
# Start DWN # Start DWN
exec dwn</code></pre> exec dwn</code></pre>
<p style="margin-top: 1rem; font-size: 0.875rem; color: var(--text-muted);"> <p style="margin-top: 1rem; font-size: 0.875rem; color: var(--text-muted);">
@ -372,15 +345,12 @@ DesktopNames=DWN</code></pre>
</div> </div>
</div> </div>
</section> </section>
<!-- Testing -->
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<h2>Testing in a Nested X Server</h2> <h2>Testing in a Nested X Server</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;"> <p style="color: var(--text-muted); margin-bottom: 2rem;">
Test DWN without leaving your current session using Xephyr. Test DWN without leaving your current session using Xephyr.
</p> </p>
<div class="card"> <div class="card">
<h3>Using make run</h3> <h3>Using make run</h3>
<p>The easiest way to test DWN safely:</p> <p>The easiest way to test DWN safely:</p>
@ -392,14 +362,12 @@ DesktopNames=DWN</code></pre>
# Debian/Ubuntu: sudo apt install xserver-xephyr # Debian/Ubuntu: sudo apt install xserver-xephyr
# Fedora: sudo dnf install xorg-x11-server-Xephyr # Fedora: sudo dnf install xorg-x11-server-Xephyr
# Arch: sudo pacman -S xorg-server-xephyr # Arch: sudo pacman -S xorg-server-xephyr
# Run DWN in a nested window # Run DWN in a nested window
make run</code></pre> make run</code></pre>
<p style="margin-top: 1rem; color: var(--text-muted);"> <p style="margin-top: 1rem; color: var(--text-muted);">
This opens a 1280x720 window running DWN. Perfect for experimenting with configuration changes. This opens a 1280x720 window running DWN. Perfect for experimenting with configuration changes.
</p> </p>
</div> </div>
<div class="card" style="margin-top: 1.5rem;"> <div class="card" style="margin-top: 1.5rem;">
<h3>Manual Xephyr Setup</h3> <h3>Manual Xephyr Setup</h3>
<p>For more control over the test environment:</p> <p>For more control over the test environment:</p>
@ -409,21 +377,16 @@ make run</code></pre>
</div> </div>
<pre><code># Start Xephyr on display :1 <pre><code># Start Xephyr on display :1
Xephyr :1 -screen 1920x1080 & Xephyr :1 -screen 1920x1080 &
# Run DWN on that display # Run DWN on that display
DISPLAY=:1 ./dwn DISPLAY=:1 ./dwn
# Open a terminal in the test environment # Open a terminal in the test environment
DISPLAY=:1 xterm &</code></pre> DISPLAY=:1 xterm &</code></pre>
</div> </div>
</div> </div>
</section> </section>
<!-- Post-install -->
<section class="section section-alt"> <section class="section section-alt">
<div class="container"> <div class="container">
<h2>Post-Installation</h2> <h2>Post-Installation</h2>
<div class="steps"> <div class="steps">
<div class="step"> <div class="step">
<div class="step-number">1</div> <div class="step-number">1</div>
@ -437,7 +400,6 @@ DISPLAY=:1 xterm &</code></pre>
<pre><code>mkdir -p ~/.config/dwn</code></pre> <pre><code>mkdir -p ~/.config/dwn</code></pre>
</div> </div>
</div> </div>
<div class="step"> <div class="step">
<div class="step-number">2</div> <div class="step-number">2</div>
<div class="step-content"> <div class="step-content">
@ -446,7 +408,6 @@ DISPLAY=:1 xterm &</code></pre>
that will teach you all the essential shortcuts.</p> that will teach you all the essential shortcuts.</p>
</div> </div>
</div> </div>
<div class="step"> <div class="step">
<div class="step-number">3</div> <div class="step-number">3</div>
<div class="step-content"> <div class="step-content">
@ -454,7 +415,6 @@ DISPLAY=:1 xterm &</code></pre>
<p>Press <kbd>Super</kbd> + <kbd>S</kbd> to see a complete list of keyboard shortcuts.</p> <p>Press <kbd>Super</kbd> + <kbd>S</kbd> to see a complete list of keyboard shortcuts.</p>
</div> </div>
</div> </div>
<div class="step"> <div class="step">
<div class="step-number">4</div> <div class="step-number">4</div>
<div class="step-content"> <div class="step-content">
@ -465,12 +425,9 @@ DISPLAY=:1 xterm &</code></pre>
</div> </div>
</div> </div>
</section> </section>
<!-- Troubleshooting -->
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<h2>Troubleshooting</h2> <h2>Troubleshooting</h2>
<div class="faq-item"> <div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)"> <button class="faq-question" onclick="toggleFaq(this)">
DWN doesn't start - "cannot open display" DWN doesn't start - "cannot open display"
@ -486,7 +443,6 @@ DISPLAY=:1 xterm &</code></pre>
</div> </div>
</div> </div>
</div> </div>
<div class="faq-item"> <div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)"> <button class="faq-question" onclick="toggleFaq(this)">
Build fails - "pkg-config: command not found" Build fails - "pkg-config: command not found"
@ -500,7 +456,6 @@ sudo pacman -S pkg-config # Arch</code></pre>
</div> </div>
</div> </div>
</div> </div>
<div class="faq-item"> <div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)"> <button class="faq-question" onclick="toggleFaq(this)">
Missing header files during build Missing header files during build
@ -513,7 +468,6 @@ sudo pacman -S pkg-config # Arch</code></pre>
</div> </div>
</div> </div>
</div> </div>
<div class="faq-item"> <div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)"> <button class="faq-question" onclick="toggleFaq(this)">
Keyboard shortcuts don't work Keyboard shortcuts don't work
@ -529,7 +483,6 @@ sudo pacman -S pkg-config # Arch</code></pre>
</div> </div>
</div> </div>
</div> </div>
<div class="faq-item"> <div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)"> <button class="faq-question" onclick="toggleFaq(this)">
Fonts look bad or missing Fonts look bad or missing
@ -545,8 +498,6 @@ sudo dnf install dejavu-fonts-all liberation-fonts # Fedora</code></pre>
</div> </div>
</div> </div>
</section> </section>
<!-- CTA -->
<section class="section section-alt"> <section class="section section-alt">
<div class="container" style="text-align: center;"> <div class="container" style="text-align: center;">
<h2>Installation Complete?</h2> <h2>Installation Complete?</h2>
@ -560,7 +511,6 @@ sudo dnf install dejavu-fonts-all liberation-fonts # Fedora</code></pre>
</div> </div>
</section> </section>
</main> </main>
<footer> <footer>
<div class="container"> <div class="container">
<div class="footer-grid"> <div class="footer-grid">
@ -604,7 +554,6 @@ sudo dnf install dejavu-fonts-all liberation-fonts # Fedora</code></pre>
</div> </div>
</div> </div>
</footer> </footer>
<script src="js/main.js"></script> <script src="js/main.js"></script>
</body> </body>
</html> </html>

View File

@ -37,7 +37,6 @@
</div> </div>
</nav> </nav>
</header> </header>
<main> <main>
<section class="hero" style="padding: 8rem 0 4rem;"> <section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content"> <div class="container hero-content">
@ -48,14 +47,11 @@
</p> </p>
</div> </div>
</section> </section>
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<div class="search-box"> <div class="search-box">
<input type="text" id="shortcut-search" placeholder="Search shortcuts..." onkeyup="filterShortcuts()"> <input type="text" id="shortcut-search" placeholder="Search shortcuts..." onkeyup="filterShortcuts()">
</div> </div>
<!-- Application Launchers -->
<h2 id="launchers">Application Launchers</h2> <h2 id="launchers">Application Launchers</h2>
<div class="table-wrapper"> <div class="table-wrapper">
<table class="shortcuts-table"> <table class="shortcuts-table">
@ -89,8 +85,6 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Window Management -->
<h2 id="windows">Window Management</h2> <h2 id="windows">Window Management</h2>
<div class="table-wrapper"> <div class="table-wrapper">
<table class="shortcuts-table"> <table class="shortcuts-table">
@ -128,8 +122,6 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Workspace Navigation -->
<h2 id="workspaces">Workspace Navigation</h2> <h2 id="workspaces">Workspace Navigation</h2>
<div class="table-wrapper"> <div class="table-wrapper">
<table class="shortcuts-table"> <table class="shortcuts-table">
@ -223,8 +215,6 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Layout Control -->
<h2 id="layouts">Layout Control</h2> <h2 id="layouts">Layout Control</h2>
<div class="table-wrapper"> <div class="table-wrapper">
<table class="shortcuts-table"> <table class="shortcuts-table">
@ -258,8 +248,6 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- AI Features -->
<h2 id="ai">AI Features</h2> <h2 id="ai">AI Features</h2>
<p style="color: var(--text-muted); margin-bottom: 1rem;"> <p style="color: var(--text-muted); margin-bottom: 1rem;">
These shortcuts require API keys to be configured. See <a href="ai-features.html">AI Features</a> for setup. These shortcuts require API keys to be configured. See <a href="ai-features.html">AI Features</a> for setup.
@ -288,8 +276,6 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- News Ticker -->
<h2 id="news">News Ticker</h2> <h2 id="news">News Ticker</h2>
<p style="color: var(--text-muted); margin-bottom: 1rem;"> <p style="color: var(--text-muted); margin-bottom: 1rem;">
Navigate the scrolling news ticker displayed in the bottom panel. Navigate the scrolling news ticker displayed in the bottom panel.
@ -318,8 +304,6 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Help & System -->
<h2 id="system">Help & System</h2> <h2 id="system">Help & System</h2>
<div class="table-wrapper"> <div class="table-wrapper">
<table class="shortcuts-table"> <table class="shortcuts-table">
@ -345,12 +329,9 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Printable Reference -->
<div class="card" style="margin-top: 3rem;"> <div class="card" style="margin-top: 3rem;">
<h3>Printable Quick Reference</h3> <h3>Printable Quick Reference</h3>
<p>Essential shortcuts to memorize when starting with DWN:</p> <p>Essential shortcuts to memorize when starting with DWN:</p>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;"> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;">
<div> <div>
<h4 style="color: var(--primary); margin-bottom: 0.75rem;">Must Know</h4> <h4 style="color: var(--primary); margin-bottom: 0.75rem;">Must Know</h4>
@ -397,7 +378,6 @@
</div> </div>
</section> </section>
</main> </main>
<footer> <footer>
<div class="container"> <div class="container">
<div class="footer-grid"> <div class="footer-grid">
@ -441,7 +421,6 @@
</div> </div>
</div> </div>
</footer> </footer>
<script src="js/main.js"></script> <script src="js/main.js"></script>
</body> </body>
</html> </html>

View File

@ -17,21 +17,17 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
/* API endpoints */
#define OPENROUTER_URL "https://openrouter.ai/api/v1/chat/completions" #define OPENROUTER_URL "https://openrouter.ai/api/v1/chat/completions"
/* Request queue */
static AIRequest *request_queue = NULL; static AIRequest *request_queue = NULL;
static CURLM *curl_multi = NULL; static CURLM *curl_multi = NULL;
static AIContext current_context; static AIContext current_context;
/* Response buffer for curl */
typedef struct { typedef struct {
char *data; char *data;
size_t size; size_t size;
} ResponseBuffer; } ResponseBuffer;
/* ========== CURL callbacks ========== */
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp)
{ {
@ -51,7 +47,6 @@ static size_t write_callback(void *contents, size_t size, size_t nmemb, void *us
return realsize; return realsize;
} }
/* ========== Initialization ========== */
bool ai_init(void) bool ai_init(void)
{ {
@ -62,10 +57,9 @@ bool ai_init(void)
if (dwn->config->openrouter_api_key[0] == '\0') { if (dwn->config->openrouter_api_key[0] == '\0') {
LOG_INFO("AI features disabled (no OPENROUTER_API_KEY)"); LOG_INFO("AI features disabled (no OPENROUTER_API_KEY)");
dwn->ai_enabled = false; dwn->ai_enabled = false;
return true; /* Not an error, just disabled */ return true;
} }
/* Initialize curl */
curl_global_init(CURL_GLOBAL_DEFAULT); curl_global_init(CURL_GLOBAL_DEFAULT);
curl_multi = curl_multi_init(); curl_multi = curl_multi_init();
@ -82,7 +76,6 @@ bool ai_init(void)
void ai_cleanup(void) void ai_cleanup(void)
{ {
/* Cancel all pending requests */
while (request_queue != NULL) { while (request_queue != NULL) {
AIRequest *next = request_queue->next; AIRequest *next = request_queue->next;
if (request_queue->prompt) free(request_queue->prompt); if (request_queue->prompt) free(request_queue->prompt);
@ -104,7 +97,6 @@ bool ai_is_available(void)
return dwn != NULL && dwn->ai_enabled; return dwn != NULL && dwn->ai_enabled;
} }
/* ========== API calls ========== */
AIRequest *ai_send_request(const char *prompt, void (*callback)(AIRequest *)) AIRequest *ai_send_request(const char *prompt, void (*callback)(AIRequest *))
{ {
@ -117,11 +109,9 @@ AIRequest *ai_send_request(const char *prompt, void (*callback)(AIRequest *))
req->state = AI_STATE_PENDING; req->state = AI_STATE_PENDING;
req->callback = callback; req->callback = callback;
/* Build JSON request body */
char *json_prompt = dwn_malloc(strlen(prompt) * 2 + 256); char *json_prompt = dwn_malloc(strlen(prompt) * 2 + 256);
char *escaped_prompt = dwn_malloc(strlen(prompt) * 2 + 1); char *escaped_prompt = dwn_malloc(strlen(prompt) * 2 + 1);
/* Escape special characters in prompt */
const char *src = prompt; const char *src = prompt;
char *dst = escaped_prompt; char *dst = escaped_prompt;
while (*src) { while (*src) {
@ -144,7 +134,6 @@ AIRequest *ai_send_request(const char *prompt, void (*callback)(AIRequest *))
dwn_free(escaped_prompt); dwn_free(escaped_prompt);
/* Create curl easy handle */
CURL *easy = curl_easy_init(); CURL *easy = curl_easy_init();
if (easy == NULL) { if (easy == NULL) {
dwn_free(json_prompt); dwn_free(json_prompt);
@ -153,10 +142,8 @@ AIRequest *ai_send_request(const char *prompt, void (*callback)(AIRequest *))
return NULL; return NULL;
} }
/* Response buffer */
ResponseBuffer *response = dwn_calloc(1, sizeof(ResponseBuffer)); ResponseBuffer *response = dwn_calloc(1, sizeof(ResponseBuffer));
/* Set curl options */
struct curl_slist *headers = NULL; struct curl_slist *headers = NULL;
char auth_header[300]; char auth_header[300];
snprintf(auth_header, sizeof(auth_header), "Authorization: Bearer %s", snprintf(auth_header, sizeof(auth_header), "Authorization: Bearer %s",
@ -175,19 +162,15 @@ AIRequest *ai_send_request(const char *prompt, void (*callback)(AIRequest *))
curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, 1L); curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(easy, CURLOPT_SSL_VERIFYHOST, 2L); curl_easy_setopt(easy, CURLOPT_SSL_VERIFYHOST, 2L);
/* Add to multi handle */
curl_multi_add_handle(curl_multi, easy); curl_multi_add_handle(curl_multi, easy);
/* Add to queue */
req->next = request_queue; req->next = request_queue;
request_queue = req; request_queue = req;
/* Store response buffer pointer for cleanup */
req->user_data = response; req->user_data = response;
LOG_DEBUG("AI request sent: %.50s...", prompt); LOG_DEBUG("AI request sent: %.50s...", prompt);
/* Note: json_prompt and headers will be freed after request completes */
return req; return req;
} }
@ -198,7 +181,6 @@ void ai_cancel_request(AIRequest *req)
return; return;
} }
/* Remove from queue */
AIRequest **pp = &request_queue; AIRequest **pp = &request_queue;
while (*pp != NULL) { while (*pp != NULL) {
if (*pp == req) { if (*pp == req) {
@ -229,7 +211,6 @@ void ai_process_pending(void)
int running_handles; int running_handles;
curl_multi_perform(curl_multi, &running_handles); curl_multi_perform(curl_multi, &running_handles);
/* Check for completed requests */
CURLMsg *msg; CURLMsg *msg;
int msgs_left; int msgs_left;
@ -244,8 +225,6 @@ void ai_process_pending(void)
ResponseBuffer *buf = (ResponseBuffer *)req->user_data; ResponseBuffer *buf = (ResponseBuffer *)req->user_data;
if (msg->data.result == CURLE_OK && buf != NULL && buf->data != NULL) { if (msg->data.result == CURLE_OK && buf != NULL && buf->data != NULL) {
/* Parse response using cJSON */
/* OpenRouter format: {"choices":[{"message":{"content":"..."}}]} */
cJSON *root = cJSON_Parse(buf->data); cJSON *root = cJSON_Parse(buf->data);
if (root != NULL) { if (root != NULL) {
cJSON *choices = cJSON_GetObjectItemCaseSensitive(root, "choices"); cJSON *choices = cJSON_GetObjectItemCaseSensitive(root, "choices");
@ -260,7 +239,6 @@ void ai_process_pending(void)
} }
} }
} }
/* Check for error in response */
if (req->state != AI_STATE_COMPLETED) { if (req->state != AI_STATE_COMPLETED) {
cJSON *error = cJSON_GetObjectItemCaseSensitive(root, "error"); cJSON *error = cJSON_GetObjectItemCaseSensitive(root, "error");
if (error != NULL) { if (error != NULL) {
@ -276,7 +254,6 @@ void ai_process_pending(void)
} }
if (req->state != AI_STATE_COMPLETED && req->state != AI_STATE_ERROR) { if (req->state != AI_STATE_COMPLETED && req->state != AI_STATE_ERROR) {
/* Fallback: return raw response for debugging */
req->response = dwn_strdup(buf->data); req->response = dwn_strdup(buf->data);
req->state = AI_STATE_COMPLETED; req->state = AI_STATE_COMPLETED;
LOG_WARN("Could not parse AI response, returning raw"); LOG_WARN("Could not parse AI response, returning raw");
@ -286,12 +263,10 @@ void ai_process_pending(void)
LOG_ERROR("AI request failed: %s", curl_easy_strerror(msg->data.result)); LOG_ERROR("AI request failed: %s", curl_easy_strerror(msg->data.result));
} }
/* Call callback */
if (req->callback != NULL) { if (req->callback != NULL) {
req->callback(req); req->callback(req);
} }
/* Cleanup */
if (buf != NULL) { if (buf != NULL) {
if (buf->data) free(buf->data); if (buf->data) free(buf->data);
dwn_free(buf); dwn_free(buf);
@ -304,7 +279,6 @@ void ai_process_pending(void)
} }
} }
/* ========== Context analysis ========== */
void ai_update_context(void) void ai_update_context(void)
{ {
@ -318,7 +292,6 @@ void ai_update_context(void)
"%s", ws->focused->class); "%s", ws->focused->class);
} }
/* Build list of windows on current workspace */
int offset = 0; int offset = 0;
for (Client *c = dwn->client_list; c != NULL; c = c->next) { for (Client *c = dwn->client_list; c != NULL; c = c->next) {
if (c->workspace == (unsigned int)dwn->current_workspace) { if (c->workspace == (unsigned int)dwn->current_workspace) {
@ -335,7 +308,6 @@ void ai_update_context(void)
const char *ai_analyze_task(void) const char *ai_analyze_task(void)
{ {
/* Analyze based on focused window class */
const char *class = current_context.focused_class; const char *class = current_context.focused_class;
if (strstr(class, "code") || strstr(class, "Code") || if (strstr(class, "code") || strstr(class, "Code") ||
@ -359,7 +331,6 @@ const char *ai_analyze_task(void)
const char *ai_suggest_window(void) const char *ai_suggest_window(void)
{ {
/* Simple heuristic suggestion */
const char *task = ai_analyze_task(); const char *task = ai_analyze_task();
if (strcmp(task, "coding") == 0) { if (strcmp(task, "coding") == 0) {
@ -374,12 +345,10 @@ const char *ai_suggest_window(void)
const char *ai_suggest_app(void) const char *ai_suggest_app(void)
{ {
return NULL; /* Would require more context */ return NULL;
} }
/* ========== Command palette ========== */
/* Callback for AI command response */
static void ai_command_response_callback(AIRequest *req) static void ai_command_response_callback(AIRequest *req)
{ {
if (req == NULL) { if (req == NULL) {
@ -387,15 +356,12 @@ static void ai_command_response_callback(AIRequest *req)
} }
if (req->state == AI_STATE_COMPLETED && req->response != NULL) { if (req->state == AI_STATE_COMPLETED && req->response != NULL) {
/* Check if response contains a command to execute */
/* Format: [RUN: command] or [EXEC: command] */
char *run_cmd = strstr(req->response, "[RUN:"); char *run_cmd = strstr(req->response, "[RUN:");
if (run_cmd == NULL) { if (run_cmd == NULL) {
run_cmd = strstr(req->response, "[EXEC:"); run_cmd = strstr(req->response, "[EXEC:");
} }
if (run_cmd != NULL) { if (run_cmd != NULL) {
/* Extract command */
char *cmd_start = strchr(run_cmd, ':'); char *cmd_start = strchr(run_cmd, ':');
if (cmd_start != NULL) { if (cmd_start != NULL) {
cmd_start++; cmd_start++;
@ -408,7 +374,6 @@ static void ai_command_response_callback(AIRequest *req)
strncpy(cmd, cmd_start, cmd_len); strncpy(cmd, cmd_start, cmd_len);
cmd[cmd_len] = '\0'; cmd[cmd_len] = '\0';
/* Trim trailing spaces */
while (cmd_len > 0 && cmd[cmd_len - 1] == ' ') { while (cmd_len > 0 && cmd[cmd_len - 1] == ' ') {
cmd[--cmd_len] = '\0'; cmd[--cmd_len] = '\0';
} }
@ -420,15 +385,12 @@ static void ai_command_response_callback(AIRequest *req)
} }
} }
} else { } else {
/* No command, just show response */
notification_show("DWN AI", "Response", req->response, NULL, 8000); notification_show("DWN AI", "Response", req->response, NULL, 8000);
} }
} else { } else {
notification_show("DWN AI", "Error", "Failed to get AI response", NULL, 3000); notification_show("DWN AI", "Error", "Failed to get AI response", NULL, 3000);
} }
/* Cleanup - don't free req itself, it's managed by the queue */
/* The queue will be cleaned up separately */
} }
void ai_show_command_palette(void) void ai_show_command_palette(void)
@ -440,10 +402,8 @@ void ai_show_command_palette(void)
return; return;
} }
/* Check if dmenu or rofi is available */
char *input = NULL; char *input = NULL;
/* Try dmenu first, then rofi */
if (spawn("command -v dmenu >/dev/null 2>&1") == 0) { if (spawn("command -v dmenu >/dev/null 2>&1") == 0) {
input = spawn_capture("echo '' | dmenu -p 'Ask AI:'"); input = spawn_capture("echo '' | dmenu -p 'Ask AI:'");
} else if (spawn("command -v rofi >/dev/null 2>&1") == 0) { } else if (spawn("command -v rofi >/dev/null 2>&1") == 0) {
@ -466,10 +426,8 @@ void ai_show_command_palette(void)
LOG_DEBUG("AI command palette input: %s", input); LOG_DEBUG("AI command palette input: %s", input);
/* Show "thinking" notification */
notification_show("DWN AI", "Processing...", input, NULL, 2000); notification_show("DWN AI", "Processing...", input, NULL, 2000);
/* Build context-aware prompt */
ai_update_context(); ai_update_context();
const char *task = ai_analyze_task(); const char *task = ai_analyze_task();
@ -492,7 +450,6 @@ void ai_show_command_palette(void)
dwn_free(input); dwn_free(input);
/* Send request */
ai_send_request(prompt, ai_command_response_callback); ai_send_request(prompt, ai_command_response_callback);
} }
@ -504,7 +461,6 @@ void ai_execute_command(const char *command)
LOG_DEBUG("AI executing command: %s", command); LOG_DEBUG("AI executing command: %s", command);
/* Send to AI for interpretation */
char prompt[512]; char prompt[512];
snprintf(prompt, sizeof(prompt), snprintf(prompt, sizeof(prompt),
"User command: %s\nCurrent task: %s\nRespond with a single action to take.", "User command: %s\nCurrent task: %s\nRespond with a single action to take.",
@ -513,7 +469,6 @@ void ai_execute_command(const char *command)
ai_send_request(prompt, NULL); ai_send_request(prompt, NULL);
} }
/* ========== Smart features ========== */
void ai_auto_organize_workspace(void) void ai_auto_organize_workspace(void)
{ {
@ -530,19 +485,16 @@ void ai_analyze_workflow(void)
LOG_DEBUG("AI workflow analysis (placeholder)"); LOG_DEBUG("AI workflow analysis (placeholder)");
} }
/* ========== Notification intelligence ========== */
bool ai_should_show_notification(const char *app, const char *summary) bool ai_should_show_notification(const char *app, const char *summary)
{ {
/* Simple filtering - could be enhanced with AI */
(void)app; (void)app;
(void)summary; (void)summary;
return true; /* Show all by default */ return true;
} }
int ai_notification_priority(const char *app, const char *summary) int ai_notification_priority(const char *app, const char *summary)
{ {
/* Simple priority assignment */
if (strstr(summary, "urgent") || strstr(summary, "Urgent") || if (strstr(summary, "urgent") || strstr(summary, "Urgent") ||
strstr(summary, "error") || strstr(summary, "Error")) { strstr(summary, "error") || strstr(summary, "Error")) {
return 3; return 3;
@ -554,11 +506,9 @@ int ai_notification_priority(const char *app, const char *summary)
return 1; return 1;
} }
/* ========== Performance monitoring ========== */
void ai_monitor_performance(void) void ai_monitor_performance(void)
{ {
/* Read from /proc for basic metrics */
LOG_DEBUG("AI performance monitoring (placeholder)"); LOG_DEBUG("AI performance monitoring (placeholder)");
} }
@ -567,7 +517,6 @@ const char *ai_performance_suggestion(void)
return NULL; return NULL;
} }
/* ========== Exa Semantic Search ========== */
#define EXA_API_URL "https://api.exa.ai/search" #define EXA_API_URL "https://api.exa.ai/search"
@ -579,7 +528,6 @@ bool exa_is_available(void)
dwn->config->exa_api_key[0] != '\0'; dwn->config->exa_api_key[0] != '\0';
} }
/* Parse Exa JSON response using cJSON */
static void exa_parse_response(ExaRequest *req, const char *json) static void exa_parse_response(ExaRequest *req, const char *json)
{ {
if (req == NULL || json == NULL) { if (req == NULL || json == NULL) {
@ -607,21 +555,18 @@ static void exa_parse_response(ExaRequest *req, const char *json)
ExaSearchResult *res = &req->results[req->result_count]; ExaSearchResult *res = &req->results[req->result_count];
/* Extract title */
cJSON *title = cJSON_GetObjectItemCaseSensitive(item, "title"); cJSON *title = cJSON_GetObjectItemCaseSensitive(item, "title");
if (cJSON_IsString(title) && title->valuestring != NULL) { if (cJSON_IsString(title) && title->valuestring != NULL) {
strncpy(res->title, title->valuestring, sizeof(res->title) - 1); strncpy(res->title, title->valuestring, sizeof(res->title) - 1);
res->title[sizeof(res->title) - 1] = '\0'; res->title[sizeof(res->title) - 1] = '\0';
} }
/* Extract URL */
cJSON *url = cJSON_GetObjectItemCaseSensitive(item, "url"); cJSON *url = cJSON_GetObjectItemCaseSensitive(item, "url");
if (cJSON_IsString(url) && url->valuestring != NULL) { if (cJSON_IsString(url) && url->valuestring != NULL) {
strncpy(res->url, url->valuestring, sizeof(res->url) - 1); strncpy(res->url, url->valuestring, sizeof(res->url) - 1);
res->url[sizeof(res->url) - 1] = '\0'; res->url[sizeof(res->url) - 1] = '\0';
} }
/* Extract text/snippet if available */
cJSON *text = cJSON_GetObjectItemCaseSensitive(item, "text"); cJSON *text = cJSON_GetObjectItemCaseSensitive(item, "text");
if (cJSON_IsString(text) && text->valuestring != NULL) { if (cJSON_IsString(text) && text->valuestring != NULL) {
strncpy(res->snippet, text->valuestring, sizeof(res->snippet) - 1); strncpy(res->snippet, text->valuestring, sizeof(res->snippet) - 1);
@ -647,11 +592,9 @@ ExaRequest *exa_search(const char *query, void (*callback)(ExaRequest *))
req->callback = callback; req->callback = callback;
req->result_count = 0; req->result_count = 0;
/* Build JSON request */
char *json_query = dwn_malloc(strlen(query) * 2 + 256); char *json_query = dwn_malloc(strlen(query) * 2 + 256);
char *escaped = dwn_malloc(strlen(query) * 2 + 1); char *escaped = dwn_malloc(strlen(query) * 2 + 1);
/* Escape query string */
const char *src = query; const char *src = query;
char *dst = escaped; char *dst = escaped;
while (*src) { while (*src) {
@ -667,7 +610,6 @@ ExaRequest *exa_search(const char *query, void (*callback)(ExaRequest *))
escaped); escaped);
dwn_free(escaped); dwn_free(escaped);
/* Create curl handle */
CURL *easy = curl_easy_init(); CURL *easy = curl_easy_init();
if (easy == NULL) { if (easy == NULL) {
dwn_free(json_query); dwn_free(json_query);
@ -676,10 +618,8 @@ ExaRequest *exa_search(const char *query, void (*callback)(ExaRequest *))
return NULL; return NULL;
} }
/* Response buffer */
ResponseBuffer *response = dwn_calloc(1, sizeof(ResponseBuffer)); ResponseBuffer *response = dwn_calloc(1, sizeof(ResponseBuffer));
/* Set headers */
struct curl_slist *headers = NULL; struct curl_slist *headers = NULL;
char api_header[300]; char api_header[300];
snprintf(api_header, sizeof(api_header), "x-api-key: %s", snprintf(api_header, sizeof(api_header), "x-api-key: %s",
@ -698,13 +638,11 @@ ExaRequest *exa_search(const char *query, void (*callback)(ExaRequest *))
curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, 1L); curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(easy, CURLOPT_SSL_VERIFYHOST, 2L); curl_easy_setopt(easy, CURLOPT_SSL_VERIFYHOST, 2L);
/* Add to multi handle */
if (curl_multi == NULL) { if (curl_multi == NULL) {
curl_multi = curl_multi_init(); curl_multi = curl_multi_init();
} }
curl_multi_add_handle(curl_multi, easy); curl_multi_add_handle(curl_multi, easy);
/* Add to queue */
req->next = exa_queue; req->next = exa_queue;
exa_queue = req; exa_queue = req;
req->user_data = response; req->user_data = response;
@ -733,7 +671,6 @@ void exa_process_pending(void)
curl_easy_getinfo(easy, CURLINFO_PRIVATE, &req); curl_easy_getinfo(easy, CURLINFO_PRIVATE, &req);
/* Check if this is an Exa request (in exa_queue) */
bool is_exa = false; bool is_exa = false;
for (ExaRequest *r = exa_queue; r != NULL; r = r->next) { for (ExaRequest *r = exa_queue; r != NULL; r = r->next) {
if (r == req) { if (r == req) {
@ -757,13 +694,11 @@ void exa_process_pending(void)
req->callback(req); req->callback(req);
} }
/* Cleanup buffer */
if (buf != NULL) { if (buf != NULL) {
if (buf->data) free(buf->data); if (buf->data) free(buf->data);
dwn_free(buf); dwn_free(buf);
} }
/* Remove from queue */
ExaRequest **pp = &exa_queue; ExaRequest **pp = &exa_queue;
while (*pp != NULL) { while (*pp != NULL) {
if (*pp == req) { if (*pp == req) {
@ -780,7 +715,6 @@ void exa_process_pending(void)
} }
} }
/* Callback for app launcher search */
static void exa_launcher_callback(ExaRequest *req) static void exa_launcher_callback(ExaRequest *req)
{ {
if (req == NULL || req->state != AI_STATE_COMPLETED) { if (req == NULL || req->state != AI_STATE_COMPLETED) {
@ -793,7 +727,6 @@ static void exa_launcher_callback(ExaRequest *req)
return; return;
} }
/* Show results via dmenu/rofi - use bounded string operations */
size_t choices_size = req->result_count * 300; size_t choices_size = req->result_count * 300;
char *choices = dwn_malloc(choices_size); char *choices = dwn_malloc(choices_size);
size_t offset = 0; size_t offset = 0;
@ -807,7 +740,6 @@ static void exa_launcher_callback(ExaRequest *req)
} }
} }
/* Show in dmenu - escape choices to prevent command injection */
char *escaped_choices = shell_escape(choices); char *escaped_choices = shell_escape(choices);
char *cmd = dwn_malloc(strlen(escaped_choices) + 64); char *cmd = dwn_malloc(strlen(escaped_choices) + 64);
snprintf(cmd, strlen(escaped_choices) + 64, "echo %s | dmenu -l 10 -p 'Results:'", escaped_choices); snprintf(cmd, strlen(escaped_choices) + 64, "echo %s | dmenu -l 10 -p 'Results:'", escaped_choices);
@ -817,10 +749,8 @@ static void exa_launcher_callback(ExaRequest *req)
dwn_free(escaped_choices); dwn_free(escaped_choices);
if (selected != NULL && selected[0] != '\0') { if (selected != NULL && selected[0] != '\0') {
/* Find which result was selected and open URL */
for (int i = 0; i < req->result_count; i++) { for (int i = 0; i < req->result_count; i++) {
if (strncmp(selected, req->results[i].title, strlen(req->results[i].title)) == 0) { if (strncmp(selected, req->results[i].title, strlen(req->results[i].title)) == 0) {
/* Escape URL to prevent command injection */
char *escaped_url = shell_escape(req->results[i].url); char *escaped_url = shell_escape(req->results[i].url);
char *open_cmd = dwn_malloc(strlen(escaped_url) + 32); char *open_cmd = dwn_malloc(strlen(escaped_url) + 32);
snprintf(open_cmd, strlen(escaped_url) + 32, "xdg-open %s &", escaped_url); snprintf(open_cmd, strlen(escaped_url) + 32, "xdg-open %s &", escaped_url);
@ -847,7 +777,6 @@ void exa_show_app_launcher(void)
return; return;
} }
/* Get search query from user */
char *query = NULL; char *query = NULL;
if (spawn("command -v dmenu >/dev/null 2>&1") == 0) { if (spawn("command -v dmenu >/dev/null 2>&1") == 0) {
@ -865,7 +794,6 @@ void exa_show_app_launcher(void)
return; return;
} }
/* Remove trailing newline */
query[strcspn(query, "\n")] = '\0'; query[strcspn(query, "\n")] = '\0';
notification_show("Exa", "Searching...", query, NULL, 2000); notification_show("Exa", "Searching...", query, NULL, 2000);

View File

@ -17,13 +17,10 @@
#include <ctype.h> #include <ctype.h>
#include <pwd.h> #include <pwd.h>
/* Global state */
static AppLauncherState launcher_state = {0}; static AppLauncherState launcher_state = {0};
/* Path to recent apps file */
static char recent_file_path[512] = {0}; static char recent_file_path[512] = {0};
/* Forward declarations */
static void scan_desktop_dir(const char *dir); static void scan_desktop_dir(const char *dir);
static int parse_desktop_file(const char *path, AppEntry *entry); static int parse_desktop_file(const char *path, AppEntry *entry);
static void load_recent_apps(void); static void load_recent_apps(void);
@ -32,13 +29,11 @@ static void add_to_recent(const char *desktop_id);
static char *strip_field_codes(const char *exec); static char *strip_field_codes(const char *exec);
static int compare_apps(const void *a, const void *b); static int compare_apps(const void *a, const void *b);
/* ========== Initialization ========== */
void applauncher_init(void) void applauncher_init(void)
{ {
memset(&launcher_state, 0, sizeof(launcher_state)); memset(&launcher_state, 0, sizeof(launcher_state));
/* Set up recent file path */
const char *home = getenv("HOME"); const char *home = getenv("HOME");
if (home == NULL) { if (home == NULL) {
struct passwd *pw = getpwuid(getuid()); struct passwd *pw = getpwuid(getuid());
@ -47,15 +42,12 @@ void applauncher_init(void)
snprintf(recent_file_path, sizeof(recent_file_path), snprintf(recent_file_path, sizeof(recent_file_path),
"%s/.local/share/dwn/recent_apps", home); "%s/.local/share/dwn/recent_apps", home);
/* Ensure directory exists */
char dir_path[512]; char dir_path[512];
snprintf(dir_path, sizeof(dir_path), "%s/.local/share/dwn", home); snprintf(dir_path, sizeof(dir_path), "%s/.local/share/dwn", home);
mkdir(dir_path, 0755); mkdir(dir_path, 0755);
/* Load recent apps first */
load_recent_apps(); load_recent_apps();
/* Scan application directories */
applauncher_refresh(); applauncher_refresh();
LOG_INFO("App launcher initialized with %d applications", launcher_state.app_count); LOG_INFO("App launcher initialized with %d applications", launcher_state.app_count);
@ -70,10 +62,8 @@ void applauncher_refresh(void)
{ {
launcher_state.app_count = 0; launcher_state.app_count = 0;
/* Scan system applications */
scan_desktop_dir("/usr/share/applications"); scan_desktop_dir("/usr/share/applications");
/* Scan user applications (takes precedence) */
const char *home = getenv("HOME"); const char *home = getenv("HOME");
if (home) { if (home) {
char user_apps[512]; char user_apps[512];
@ -81,14 +71,12 @@ void applauncher_refresh(void)
scan_desktop_dir(user_apps); scan_desktop_dir(user_apps);
} }
/* Sort apps alphabetically by name */
qsort(launcher_state.apps, launcher_state.app_count, qsort(launcher_state.apps, launcher_state.app_count,
sizeof(AppEntry), compare_apps); sizeof(AppEntry), compare_apps);
LOG_DEBUG("Refreshed app list: %d applications found", launcher_state.app_count); LOG_DEBUG("Refreshed app list: %d applications found", launcher_state.app_count);
} }
/* ========== Directory Scanning ========== */
static void scan_desktop_dir(const char *dir) static void scan_desktop_dir(const char *dir)
{ {
@ -103,17 +91,14 @@ static void scan_desktop_dir(const char *dir)
break; break;
} }
/* Only process .desktop files */
size_t len = strlen(entry->d_name); size_t len = strlen(entry->d_name);
if (len < 9 || strcmp(entry->d_name + len - 8, ".desktop") != 0) { if (len < 9 || strcmp(entry->d_name + len - 8, ".desktop") != 0) {
continue; continue;
} }
/* Build full path */
char path[1024]; char path[1024];
snprintf(path, sizeof(path), "%s/%s", dir, entry->d_name); snprintf(path, sizeof(path), "%s/%s", dir, entry->d_name);
/* Check if already exists (user apps override system) */
bool exists = false; bool exists = false;
for (int i = 0; i < launcher_state.app_count; i++) { for (int i = 0; i < launcher_state.app_count; i++) {
if (strcmp(launcher_state.apps[i].desktop_id, entry->d_name) == 0) { if (strcmp(launcher_state.apps[i].desktop_id, entry->d_name) == 0) {
@ -125,7 +110,6 @@ static void scan_desktop_dir(const char *dir)
continue; continue;
} }
/* Parse the desktop file */
AppEntry app = {0}; AppEntry app = {0};
if (parse_desktop_file(path, &app) == 0) { if (parse_desktop_file(path, &app) == 0) {
snprintf(app.desktop_id, sizeof(app.desktop_id), "%s", entry->d_name); snprintf(app.desktop_id, sizeof(app.desktop_id), "%s", entry->d_name);
@ -136,7 +120,6 @@ static void scan_desktop_dir(const char *dir)
closedir(d); closedir(d);
} }
/* ========== Desktop File Parsing ========== */
static int parse_desktop_file(const char *path, AppEntry *entry) static int parse_desktop_file(const char *path, AppEntry *entry)
{ {
@ -154,14 +137,12 @@ static int parse_desktop_file(const char *path, AppEntry *entry)
entry->terminal = false; entry->terminal = false;
while (fgets(line, sizeof(line), f) != NULL) { while (fgets(line, sizeof(line), f) != NULL) {
/* Remove trailing whitespace/newline */
size_t len = strlen(line); size_t len = strlen(line);
while (len > 0 && (line[len-1] == '\n' || line[len-1] == '\r' || while (len > 0 && (line[len-1] == '\n' || line[len-1] == '\r' ||
line[len-1] == ' ' || line[len-1] == '\t')) { line[len-1] == ' ' || line[len-1] == '\t')) {
line[--len] = '\0'; line[--len] = '\0';
} }
/* Check for section header */
if (line[0] == '[') { if (line[0] == '[') {
in_desktop_entry = (strcmp(line, "[Desktop Entry]") == 0); in_desktop_entry = (strcmp(line, "[Desktop Entry]") == 0);
continue; continue;
@ -171,7 +152,6 @@ static int parse_desktop_file(const char *path, AppEntry *entry)
continue; continue;
} }
/* Parse key=value */
char *eq = strchr(line, '='); char *eq = strchr(line, '=');
if (eq == NULL) { if (eq == NULL) {
continue; continue;
@ -181,7 +161,6 @@ static int parse_desktop_file(const char *path, AppEntry *entry)
char *key = line; char *key = line;
char *value = eq + 1; char *value = eq + 1;
/* Skip localized entries (Name[en]=...) */
if (strchr(key, '[') != NULL) { if (strchr(key, '[') != NULL) {
continue; continue;
} }
@ -197,7 +176,6 @@ static int parse_desktop_file(const char *path, AppEntry *entry)
} else if (strcmp(key, "Terminal") == 0) { } else if (strcmp(key, "Terminal") == 0) {
entry->terminal = (strcasecmp(value, "true") == 0); entry->terminal = (strcasecmp(value, "true") == 0);
} else if (strcmp(key, "Type") == 0) { } else if (strcmp(key, "Type") == 0) {
/* Skip non-Application types */
if (strcmp(value, "Application") != 0) { if (strcmp(value, "Application") != 0) {
fclose(f); fclose(f);
return -1; return -1;
@ -211,7 +189,6 @@ static int parse_desktop_file(const char *path, AppEntry *entry)
fclose(f); fclose(f);
/* Must have name and exec, and not be hidden */
if (!has_name || !has_exec || entry->hidden) { if (!has_name || !has_exec || entry->hidden) {
return -1; return -1;
} }
@ -219,7 +196,6 @@ static int parse_desktop_file(const char *path, AppEntry *entry)
return 0; return 0;
} }
/* ========== Recent Apps Management ========== */
static void load_recent_apps(void) static void load_recent_apps(void)
{ {
@ -233,7 +209,6 @@ static void load_recent_apps(void)
char line[256]; char line[256];
while (fgets(line, sizeof(line), f) != NULL && while (fgets(line, sizeof(line), f) != NULL &&
launcher_state.recent_count < MAX_RECENT_APPS) { launcher_state.recent_count < MAX_RECENT_APPS) {
/* Remove newline */
size_t len = strlen(line); size_t len = strlen(line);
if (len > 0 && line[len-1] == '\n') { if (len > 0 && line[len-1] == '\n') {
line[len-1] = '\0'; line[len-1] = '\0';
@ -266,10 +241,8 @@ static void save_recent_apps(void)
static void add_to_recent(const char *desktop_id) static void add_to_recent(const char *desktop_id)
{ {
/* Remove if already in list */
for (int i = 0; i < launcher_state.recent_count; i++) { for (int i = 0; i < launcher_state.recent_count; i++) {
if (strcmp(launcher_state.recent[i], desktop_id) == 0) { if (strcmp(launcher_state.recent[i], desktop_id) == 0) {
/* Shift everything down */
for (int j = i; j > 0; j--) { for (int j = i; j > 0; j--) {
strcpy(launcher_state.recent[j], launcher_state.recent[j-1]); strcpy(launcher_state.recent[j], launcher_state.recent[j-1]);
} }
@ -279,7 +252,6 @@ static void add_to_recent(const char *desktop_id)
} }
} }
/* Add to front, shift others */
if (launcher_state.recent_count < MAX_RECENT_APPS) { if (launcher_state.recent_count < MAX_RECENT_APPS) {
launcher_state.recent_count++; launcher_state.recent_count++;
} }
@ -292,7 +264,6 @@ static void add_to_recent(const char *desktop_id)
save_recent_apps(); save_recent_apps();
} }
/* ========== Helper Functions ========== */
static char *strip_field_codes(const char *exec) static char *strip_field_codes(const char *exec)
{ {
@ -301,13 +272,11 @@ static char *strip_field_codes(const char *exec)
for (size_t i = 0; exec[i] && j < sizeof(result) - 1; i++) { for (size_t i = 0; exec[i] && j < sizeof(result) - 1; i++) {
if (exec[i] == '%' && exec[i+1]) { if (exec[i] == '%' && exec[i+1]) {
/* Skip field codes: %f, %F, %u, %U, %d, %D, %n, %N, %i, %c, %k */
char code = exec[i+1]; char code = exec[i+1];
if (code == 'f' || code == 'F' || code == 'u' || code == 'U' || if (code == 'f' || code == 'F' || code == 'u' || code == 'U' ||
code == 'd' || code == 'D' || code == 'n' || code == 'N' || code == 'd' || code == 'D' || code == 'n' || code == 'N' ||
code == 'i' || code == 'c' || code == 'k') { code == 'i' || code == 'c' || code == 'k') {
i++; /* Skip the code character */ i++;
/* Also skip trailing space if any */
if (exec[i+1] == ' ') { if (exec[i+1] == ' ') {
i++; i++;
} }
@ -318,7 +287,6 @@ static char *strip_field_codes(const char *exec)
} }
result[j] = '\0'; result[j] = '\0';
/* Trim trailing whitespace */
while (j > 0 && (result[j-1] == ' ' || result[j-1] == '\t')) { while (j > 0 && (result[j-1] == ' ' || result[j-1] == '\t')) {
result[--j] = '\0'; result[--j] = '\0';
} }
@ -333,7 +301,6 @@ static int compare_apps(const void *a, const void *b)
return strcasecmp(app_a->name, app_b->name); return strcasecmp(app_a->name, app_b->name);
} }
/* Find app by desktop_id */
static AppEntry *find_app_by_id(const char *desktop_id) static AppEntry *find_app_by_id(const char *desktop_id)
{ {
for (int i = 0; i < launcher_state.app_count; i++) { for (int i = 0; i < launcher_state.app_count; i++) {
@ -344,7 +311,6 @@ static AppEntry *find_app_by_id(const char *desktop_id)
return NULL; return NULL;
} }
/* Find app by name */
static AppEntry *find_app_by_name(const char *name) static AppEntry *find_app_by_name(const char *name)
{ {
for (int i = 0; i < launcher_state.app_count; i++) { for (int i = 0; i < launcher_state.app_count; i++) {
@ -355,7 +321,6 @@ static AppEntry *find_app_by_name(const char *name)
return NULL; return NULL;
} }
/* ========== Launcher Display ========== */
void applauncher_show(void) void applauncher_show(void)
{ {
@ -364,10 +329,6 @@ void applauncher_show(void)
return; return;
} }
/* Build the list for dmenu:
* - Recent apps first (if any)
* - Then all apps alphabetically
*/
size_t buf_size = launcher_state.app_count * 140; size_t buf_size = launcher_state.app_count * 140;
char *choices = malloc(buf_size); char *choices = malloc(buf_size);
if (choices == NULL) { if (choices == NULL) {
@ -376,7 +337,6 @@ void applauncher_show(void)
} }
choices[0] = '\0'; choices[0] = '\0';
/* Track which apps we've added (to avoid duplicates) */
bool *added = calloc(launcher_state.app_count, sizeof(bool)); bool *added = calloc(launcher_state.app_count, sizeof(bool));
if (added == NULL) { if (added == NULL) {
free(choices); free(choices);
@ -385,11 +345,9 @@ void applauncher_show(void)
size_t pos = 0; size_t pos = 0;
/* Add recent apps first */
for (int i = 0; i < launcher_state.recent_count; i++) { for (int i = 0; i < launcher_state.recent_count; i++) {
AppEntry *app = find_app_by_id(launcher_state.recent[i]); AppEntry *app = find_app_by_id(launcher_state.recent[i]);
if (app != NULL) { if (app != NULL) {
/* Mark as added */
for (int j = 0; j < launcher_state.app_count; j++) { for (int j = 0; j < launcher_state.app_count; j++) {
if (&launcher_state.apps[j] == app) { if (&launcher_state.apps[j] == app) {
added[j] = true; added[j] = true;
@ -405,7 +363,6 @@ void applauncher_show(void)
} }
} }
/* Add remaining apps */
for (int i = 0; i < launcher_state.app_count; i++) { for (int i = 0; i < launcher_state.app_count; i++) {
if (added[i]) { if (added[i]) {
continue; continue;
@ -419,12 +376,11 @@ void applauncher_show(void)
} }
if (pos > 0) { if (pos > 0) {
choices[pos-1] = '\0'; /* Remove trailing newline */ choices[pos-1] = '\0';
} }
free(added); free(added);
/* Write choices to a temp file to avoid shell escaping issues */
char tmp_path[] = "/tmp/dwn_apps_XXXXXX"; char tmp_path[] = "/tmp/dwn_apps_XXXXXX";
int tmp_fd = mkstemp(tmp_path); int tmp_fd = mkstemp(tmp_path);
if (tmp_fd < 0) { if (tmp_fd < 0) {
@ -443,7 +399,6 @@ void applauncher_show(void)
return; return;
} }
/* Run dmenu */
char cmd[1024]; char cmd[1024];
snprintf(cmd, sizeof(cmd), snprintf(cmd, sizeof(cmd),
"cat '%s' | dmenu -i -l 10 -p 'Run:'; rm -f '%s'", "cat '%s' | dmenu -i -l 10 -p 'Run:'; rm -f '%s'",
@ -458,7 +413,6 @@ void applauncher_show(void)
char selected[256] = {0}; char selected[256] = {0};
if (fgets(selected, sizeof(selected), p) != NULL) { if (fgets(selected, sizeof(selected), p) != NULL) {
/* Remove trailing newline */
size_t len = strlen(selected); size_t len = strlen(selected);
if (len > 0 && selected[len-1] == '\n') { if (len > 0 && selected[len-1] == '\n') {
selected[len-1] = '\0'; selected[len-1] = '\0';
@ -467,15 +421,13 @@ void applauncher_show(void)
pclose(p); pclose(p);
if (selected[0] == '\0') { if (selected[0] == '\0') {
return; /* User cancelled */ return;
} }
/* Find and launch the selected app */
AppEntry *app = find_app_by_name(selected); AppEntry *app = find_app_by_name(selected);
if (app != NULL) { if (app != NULL) {
applauncher_launch(app->desktop_id); applauncher_launch(app->desktop_id);
} else { } else {
/* User typed a custom command */
spawn_async(selected); spawn_async(selected);
} }
} }
@ -488,10 +440,8 @@ void applauncher_launch(const char *desktop_id)
return; return;
} }
/* Strip field codes from exec */
char *exec_cmd = strip_field_codes(app->exec); char *exec_cmd = strip_field_codes(app->exec);
/* Build command */
char cmd[1024]; char cmd[1024];
if (app->terminal) { if (app->terminal) {
const char *terminal = config_get_terminal(); const char *terminal = config_get_terminal();
@ -503,6 +453,5 @@ void applauncher_launch(const char *desktop_id)
LOG_INFO("Launching: %s (%s)", app->name, cmd); LOG_INFO("Launching: %s (%s)", app->name, cmd);
spawn_async(cmd); spawn_async(cmd);
/* Add to recent apps */
add_to_recent(desktop_id); add_to_recent(desktop_id);
} }

View File

@ -12,19 +12,16 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
/* Global atom containers */
EWMHAtoms ewmh; EWMHAtoms ewmh;
ICCCMAtoms icccm; ICCCMAtoms icccm;
MiscAtoms misc_atoms; MiscAtoms misc_atoms;
/* Helper macro for atom initialization */
#define ATOM(name) XInternAtom(display, name, False) #define ATOM(name) XInternAtom(display, name, False)
void atoms_init(Display *display) void atoms_init(Display *display)
{ {
LOG_DEBUG("Initializing X11 atoms"); LOG_DEBUG("Initializing X11 atoms");
/* EWMH root window properties */
ewmh.NET_SUPPORTED = ATOM("_NET_SUPPORTED"); ewmh.NET_SUPPORTED = ATOM("_NET_SUPPORTED");
ewmh.NET_SUPPORTING_WM_CHECK = ATOM("_NET_SUPPORTING_WM_CHECK"); ewmh.NET_SUPPORTING_WM_CHECK = ATOM("_NET_SUPPORTING_WM_CHECK");
ewmh.NET_CLIENT_LIST = ATOM("_NET_CLIENT_LIST"); ewmh.NET_CLIENT_LIST = ATOM("_NET_CLIENT_LIST");
@ -37,7 +34,6 @@ void atoms_init(Display *display)
ewmh.NET_ACTIVE_WINDOW = ATOM("_NET_ACTIVE_WINDOW"); ewmh.NET_ACTIVE_WINDOW = ATOM("_NET_ACTIVE_WINDOW");
ewmh.NET_WORKAREA = ATOM("_NET_WORKAREA"); ewmh.NET_WORKAREA = ATOM("_NET_WORKAREA");
/* EWMH client window properties */
ewmh.NET_WM_NAME = ATOM("_NET_WM_NAME"); ewmh.NET_WM_NAME = ATOM("_NET_WM_NAME");
ewmh.NET_WM_VISIBLE_NAME = ATOM("_NET_WM_VISIBLE_NAME"); ewmh.NET_WM_VISIBLE_NAME = ATOM("_NET_WM_VISIBLE_NAME");
ewmh.NET_WM_DESKTOP = ATOM("_NET_WM_DESKTOP"); ewmh.NET_WM_DESKTOP = ATOM("_NET_WM_DESKTOP");
@ -48,7 +44,6 @@ void atoms_init(Display *display)
ewmh.NET_WM_STRUT_PARTIAL = ATOM("_NET_WM_STRUT_PARTIAL"); ewmh.NET_WM_STRUT_PARTIAL = ATOM("_NET_WM_STRUT_PARTIAL");
ewmh.NET_WM_PID = ATOM("_NET_WM_PID"); ewmh.NET_WM_PID = ATOM("_NET_WM_PID");
/* Window types */
ewmh.NET_WM_WINDOW_TYPE_DESKTOP = ATOM("_NET_WM_WINDOW_TYPE_DESKTOP"); ewmh.NET_WM_WINDOW_TYPE_DESKTOP = ATOM("_NET_WM_WINDOW_TYPE_DESKTOP");
ewmh.NET_WM_WINDOW_TYPE_DOCK = ATOM("_NET_WM_WINDOW_TYPE_DOCK"); ewmh.NET_WM_WINDOW_TYPE_DOCK = ATOM("_NET_WM_WINDOW_TYPE_DOCK");
ewmh.NET_WM_WINDOW_TYPE_TOOLBAR = ATOM("_NET_WM_WINDOW_TYPE_TOOLBAR"); ewmh.NET_WM_WINDOW_TYPE_TOOLBAR = ATOM("_NET_WM_WINDOW_TYPE_TOOLBAR");
@ -59,7 +54,6 @@ void atoms_init(Display *display)
ewmh.NET_WM_WINDOW_TYPE_NORMAL = ATOM("_NET_WM_WINDOW_TYPE_NORMAL"); ewmh.NET_WM_WINDOW_TYPE_NORMAL = ATOM("_NET_WM_WINDOW_TYPE_NORMAL");
ewmh.NET_WM_WINDOW_TYPE_NOTIFICATION = ATOM("_NET_WM_WINDOW_TYPE_NOTIFICATION"); ewmh.NET_WM_WINDOW_TYPE_NOTIFICATION = ATOM("_NET_WM_WINDOW_TYPE_NOTIFICATION");
/* Window states */
ewmh.NET_WM_STATE_MODAL = ATOM("_NET_WM_STATE_MODAL"); ewmh.NET_WM_STATE_MODAL = ATOM("_NET_WM_STATE_MODAL");
ewmh.NET_WM_STATE_STICKY = ATOM("_NET_WM_STATE_STICKY"); ewmh.NET_WM_STATE_STICKY = ATOM("_NET_WM_STATE_STICKY");
ewmh.NET_WM_STATE_MAXIMIZED_VERT = ATOM("_NET_WM_STATE_MAXIMIZED_VERT"); ewmh.NET_WM_STATE_MAXIMIZED_VERT = ATOM("_NET_WM_STATE_MAXIMIZED_VERT");
@ -74,7 +68,6 @@ void atoms_init(Display *display)
ewmh.NET_WM_STATE_DEMANDS_ATTENTION = ATOM("_NET_WM_STATE_DEMANDS_ATTENTION"); ewmh.NET_WM_STATE_DEMANDS_ATTENTION = ATOM("_NET_WM_STATE_DEMANDS_ATTENTION");
ewmh.NET_WM_STATE_FOCUSED = ATOM("_NET_WM_STATE_FOCUSED"); ewmh.NET_WM_STATE_FOCUSED = ATOM("_NET_WM_STATE_FOCUSED");
/* Actions */
ewmh.NET_WM_ACTION_MOVE = ATOM("_NET_WM_ACTION_MOVE"); ewmh.NET_WM_ACTION_MOVE = ATOM("_NET_WM_ACTION_MOVE");
ewmh.NET_WM_ACTION_RESIZE = ATOM("_NET_WM_ACTION_RESIZE"); ewmh.NET_WM_ACTION_RESIZE = ATOM("_NET_WM_ACTION_RESIZE");
ewmh.NET_WM_ACTION_MINIMIZE = ATOM("_NET_WM_ACTION_MINIMIZE"); ewmh.NET_WM_ACTION_MINIMIZE = ATOM("_NET_WM_ACTION_MINIMIZE");
@ -86,21 +79,18 @@ void atoms_init(Display *display)
ewmh.NET_WM_ACTION_CHANGE_DESKTOP = ATOM("_NET_WM_ACTION_CHANGE_DESKTOP"); ewmh.NET_WM_ACTION_CHANGE_DESKTOP = ATOM("_NET_WM_ACTION_CHANGE_DESKTOP");
ewmh.NET_WM_ACTION_CLOSE = ATOM("_NET_WM_ACTION_CLOSE"); ewmh.NET_WM_ACTION_CLOSE = ATOM("_NET_WM_ACTION_CLOSE");
/* Client messages */
ewmh.NET_CLOSE_WINDOW = ATOM("_NET_CLOSE_WINDOW"); ewmh.NET_CLOSE_WINDOW = ATOM("_NET_CLOSE_WINDOW");
ewmh.NET_MOVERESIZE_WINDOW = ATOM("_NET_MOVERESIZE_WINDOW"); ewmh.NET_MOVERESIZE_WINDOW = ATOM("_NET_MOVERESIZE_WINDOW");
ewmh.NET_WM_MOVERESIZE = ATOM("_NET_WM_MOVERESIZE"); ewmh.NET_WM_MOVERESIZE = ATOM("_NET_WM_MOVERESIZE");
ewmh.NET_REQUEST_FRAME_EXTENTS = ATOM("_NET_REQUEST_FRAME_EXTENTS"); ewmh.NET_REQUEST_FRAME_EXTENTS = ATOM("_NET_REQUEST_FRAME_EXTENTS");
ewmh.NET_FRAME_EXTENTS = ATOM("_NET_FRAME_EXTENTS"); ewmh.NET_FRAME_EXTENTS = ATOM("_NET_FRAME_EXTENTS");
/* System tray */
ewmh.NET_SYSTEM_TRAY_OPCODE = ATOM("_NET_SYSTEM_TRAY_OPCODE"); ewmh.NET_SYSTEM_TRAY_OPCODE = ATOM("_NET_SYSTEM_TRAY_OPCODE");
ewmh.NET_SYSTEM_TRAY_S0 = ATOM("_NET_SYSTEM_TRAY_S0"); ewmh.NET_SYSTEM_TRAY_S0 = ATOM("_NET_SYSTEM_TRAY_S0");
ewmh.MANAGER = ATOM("MANAGER"); ewmh.MANAGER = ATOM("MANAGER");
ewmh.XEMBED = ATOM("_XEMBED"); ewmh.XEMBED = ATOM("_XEMBED");
ewmh.XEMBED_INFO = ATOM("_XEMBED_INFO"); ewmh.XEMBED_INFO = ATOM("_XEMBED_INFO");
/* ICCCM atoms */
icccm.WM_PROTOCOLS = ATOM("WM_PROTOCOLS"); icccm.WM_PROTOCOLS = ATOM("WM_PROTOCOLS");
icccm.WM_DELETE_WINDOW = ATOM("WM_DELETE_WINDOW"); icccm.WM_DELETE_WINDOW = ATOM("WM_DELETE_WINDOW");
icccm.WM_TAKE_FOCUS = ATOM("WM_TAKE_FOCUS"); icccm.WM_TAKE_FOCUS = ATOM("WM_TAKE_FOCUS");
@ -112,7 +102,6 @@ void atoms_init(Display *display)
icccm.WM_CLIENT_LEADER = ATOM("WM_CLIENT_LEADER"); icccm.WM_CLIENT_LEADER = ATOM("WM_CLIENT_LEADER");
icccm.WM_WINDOW_ROLE = ATOM("WM_WINDOW_ROLE"); icccm.WM_WINDOW_ROLE = ATOM("WM_WINDOW_ROLE");
/* Misc atoms */
misc_atoms.UTF8_STRING = ATOM("UTF8_STRING"); misc_atoms.UTF8_STRING = ATOM("UTF8_STRING");
misc_atoms.COMPOUND_TEXT = ATOM("COMPOUND_TEXT"); misc_atoms.COMPOUND_TEXT = ATOM("COMPOUND_TEXT");
misc_atoms.MOTIF_WM_HINTS = ATOM("_MOTIF_WM_HINTS"); misc_atoms.MOTIF_WM_HINTS = ATOM("_MOTIF_WM_HINTS");
@ -123,7 +112,6 @@ void atoms_init(Display *display)
LOG_DEBUG("X11 atoms initialized"); LOG_DEBUG("X11 atoms initialized");
} }
/* ========== EWMH Setup ========== */
void atoms_setup_ewmh(void) void atoms_setup_ewmh(void)
{ {
@ -134,10 +122,8 @@ void atoms_setup_ewmh(void)
Display *dpy = dwn->display; Display *dpy = dwn->display;
Window root = dwn->root; Window root = dwn->root;
/* Create a check window for _NET_SUPPORTING_WM_CHECK */
Window check = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0); Window check = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0);
/* Set _NET_SUPPORTING_WM_CHECK on root and check window */
XChangeProperty(dpy, root, ewmh.NET_SUPPORTING_WM_CHECK, XChangeProperty(dpy, root, ewmh.NET_SUPPORTING_WM_CHECK,
XA_WINDOW, 32, PropModeReplace, XA_WINDOW, 32, PropModeReplace,
(unsigned char *)&check, 1); (unsigned char *)&check, 1);
@ -145,12 +131,10 @@ void atoms_setup_ewmh(void)
XA_WINDOW, 32, PropModeReplace, XA_WINDOW, 32, PropModeReplace,
(unsigned char *)&check, 1); (unsigned char *)&check, 1);
/* Set _NET_WM_NAME on check window */
XChangeProperty(dpy, check, ewmh.NET_WM_NAME, XChangeProperty(dpy, check, ewmh.NET_WM_NAME,
misc_atoms.UTF8_STRING, 8, PropModeReplace, misc_atoms.UTF8_STRING, 8, PropModeReplace,
(unsigned char *)DWN_NAME, strlen(DWN_NAME)); (unsigned char *)DWN_NAME, strlen(DWN_NAME));
/* Set supported atoms */
Atom supported[] = { Atom supported[] = {
ewmh.NET_SUPPORTED, ewmh.NET_SUPPORTED,
ewmh.NET_SUPPORTING_WM_CHECK, ewmh.NET_SUPPORTING_WM_CHECK,
@ -176,13 +160,10 @@ void atoms_setup_ewmh(void)
XA_ATOM, 32, PropModeReplace, XA_ATOM, 32, PropModeReplace,
(unsigned char *)supported, sizeof(supported) / sizeof(Atom)); (unsigned char *)supported, sizeof(supported) / sizeof(Atom));
/* Set number of desktops */
atoms_set_number_of_desktops(MAX_WORKSPACES); atoms_set_number_of_desktops(MAX_WORKSPACES);
/* Set current desktop */
atoms_set_current_desktop(0); atoms_set_current_desktop(0);
/* Update desktop names */
atoms_update_desktop_names(); atoms_update_desktop_names();
LOG_INFO("EWMH compliance initialized"); LOG_INFO("EWMH compliance initialized");
@ -216,7 +197,6 @@ void atoms_update_desktop_names(void)
return; return;
} }
/* Build null-separated list of workspace names */
char names[MAX_WORKSPACES * 32]; char names[MAX_WORKSPACES * 32];
int offset = 0; int offset = 0;
@ -270,7 +250,6 @@ void atoms_set_number_of_desktops(int count)
(unsigned char *)&data, 1); (unsigned char *)&data, 1);
} }
/* ========== Window property helpers ========== */
bool atoms_get_window_type(Window window, Atom *type) bool atoms_get_window_type(Window window, Atom *type)
{ {
@ -343,7 +322,6 @@ char *atoms_get_window_name(Window window)
unsigned long nitems, bytes_after; unsigned long nitems, bytes_after;
unsigned char *data = NULL; unsigned char *data = NULL;
/* Try _NET_WM_NAME first (UTF-8) */
if (XGetWindowProperty(dpy, window, ewmh.NET_WM_NAME, if (XGetWindowProperty(dpy, window, ewmh.NET_WM_NAME,
0, 256, False, misc_atoms.UTF8_STRING, 0, 256, False, misc_atoms.UTF8_STRING,
&actual_type, &actual_format, &nitems, &bytes_after, &actual_type, &actual_format, &nitems, &bytes_after,
@ -353,7 +331,6 @@ char *atoms_get_window_name(Window window)
return name; return name;
} }
/* Fall back to WM_NAME */
if (XGetWindowProperty(dpy, window, icccm.WM_NAME, if (XGetWindowProperty(dpy, window, icccm.WM_NAME,
0, 256, False, XA_STRING, 0, 256, False, XA_STRING,
&actual_type, &actual_format, &nitems, &bytes_after, &actual_type, &actual_format, &nitems, &bytes_after,
@ -363,7 +340,6 @@ char *atoms_get_window_name(Window window)
return name; return name;
} }
/* Last resort: XFetchName */
char *name = NULL; char *name = NULL;
if (XFetchName(dpy, window, &name) && name != NULL) { if (XFetchName(dpy, window, &name) && name != NULL) {
char *result = dwn_strdup(name); char *result = dwn_strdup(name);
@ -398,7 +374,6 @@ bool atoms_get_wm_class(Window window, char *class_name, char *instance_name, si
return false; return false;
} }
/* ========== Protocol helpers ========== */
bool atoms_window_supports_protocol(Window window, Atom protocol) bool atoms_window_supports_protocol(Window window, Atom protocol)
{ {

View File

@ -17,11 +17,9 @@
#include <unistd.h> #include <unistd.h>
#include <X11/Xresource.h> #include <X11/Xresource.h>
/* ========== Client creation/destruction ========== */
Client *client_create(Window window) Client *client_create(Window window)
{ {
/* Defensive: validate global state before proceeding */
if (dwn == NULL || dwn->display == NULL) { if (dwn == NULL || dwn->display == NULL) {
LOG_ERROR("client_create: dwn or display is NULL"); LOG_ERROR("client_create: dwn or display is NULL");
return NULL; return NULL;
@ -46,7 +44,6 @@ Client *client_create(Window window)
client->next = NULL; client->next = NULL;
client->prev = NULL; client->prev = NULL;
/* Get initial geometry from window */
XWindowAttributes wa; XWindowAttributes wa;
int orig_width = 640, orig_height = 480; int orig_width = 640, orig_height = 480;
if (XGetWindowAttributes(dwn->display, window, &wa)) { if (XGetWindowAttributes(dwn->display, window, &wa)) {
@ -54,7 +51,6 @@ Client *client_create(Window window)
orig_height = wa.height; orig_height = wa.height;
} }
/* Calculate work area (account for panels) */
int work_x = 0; int work_x = 0;
int work_y = 0; int work_y = 0;
int work_width = dwn->screen_width; int work_width = dwn->screen_width;
@ -70,28 +66,23 @@ Client *client_create(Window window)
} }
} }
/* Set size to 75% of work area or original size, whichever is larger */
int target_width = (work_width * 75) / 100; int target_width = (work_width * 75) / 100;
int target_height = (work_height * 75) / 100; int target_height = (work_height * 75) / 100;
client->width = (orig_width > target_width) ? orig_width : target_width; client->width = (orig_width > target_width) ? orig_width : target_width;
client->height = (orig_height > target_height) ? orig_height : target_height; client->height = (orig_height > target_height) ? orig_height : target_height;
/* Clamp to work area */
if (client->width > work_width - 20) client->width = work_width - 20; if (client->width > work_width - 20) client->width = work_width - 20;
if (client->height > work_height - 20) client->height = work_height - 20; if (client->height > work_height - 20) client->height = work_height - 20;
/* Center on screen */
client->x = work_x + (work_width - client->width) / 2; client->x = work_x + (work_width - client->width) / 2;
client->y = work_y + (work_height - client->height) / 2; client->y = work_y + (work_height - client->height) / 2;
/* Save original geometry for floating restore */
client->old_x = client->x; client->old_x = client->x;
client->old_y = client->y; client->old_y = client->y;
client->old_width = client->width; client->old_width = client->width;
client->old_height = client->height; client->old_height = client->height;
/* Update properties */
client_update_title(client); client_update_title(client);
client_update_class(client); client_update_class(client);
@ -111,13 +102,11 @@ void client_destroy(Client *client)
dwn_free(client); dwn_free(client);
} }
/* Sync log for debugging - disabled in production */
static inline void client_sync_log(const char *msg) static inline void client_sync_log(const char *msg)
{ {
(void)msg; /* No-op - enable for debugging */ (void)msg;
} }
/* ========== Client management ========== */
Client *client_manage(Window window) Client *client_manage(Window window)
{ {
@ -128,14 +117,12 @@ Client *client_manage(Window window)
return NULL; return NULL;
} }
/* Check if already managed */
Client *existing = client_find_by_window(window); Client *existing = client_find_by_window(window);
if (existing != NULL) { if (existing != NULL) {
client_sync_log("client_manage: already managed"); client_sync_log("client_manage: already managed");
return existing; return existing;
} }
/* Check window type - don't manage docks, desktops */
if (client_is_dock(window) || client_is_desktop(window)) { if (client_is_dock(window) || client_is_desktop(window)) {
LOG_DEBUG("Skipping dock/desktop window: %lu", window); LOG_DEBUG("Skipping dock/desktop window: %lu", window);
client_sync_log("client_manage: skip dock/desktop"); client_sync_log("client_manage: skip dock/desktop");
@ -145,7 +132,6 @@ Client *client_manage(Window window)
LOG_DEBUG("Managing window: %lu", window); LOG_DEBUG("Managing window: %lu", window);
client_sync_log("client_manage: creating client"); client_sync_log("client_manage: creating client");
/* Create client */
Client *client = client_create(window); Client *client = client_create(window);
if (client == NULL) { if (client == NULL) {
LOG_ERROR("client_manage: failed to create client for window %lu", window); LOG_ERROR("client_manage: failed to create client for window %lu", window);
@ -155,19 +141,16 @@ Client *client_manage(Window window)
client_sync_log("client_manage: checking dialog"); client_sync_log("client_manage: checking dialog");
/* Check if it should be floating (dialogs, etc.) */
if (client_is_dialog(window)) { if (client_is_dialog(window)) {
client->flags |= CLIENT_FLOATING; client->flags |= CLIENT_FLOATING;
} }
client_sync_log("client_manage: creating frame"); client_sync_log("client_manage: creating frame");
/* Create frame with decorations */
client_create_frame(client); client_create_frame(client);
client_sync_log("client_manage: verifying window"); client_sync_log("client_manage: verifying window");
/* Verify window still exists before reparenting */
XWindowAttributes wa; XWindowAttributes wa;
if (!XGetWindowAttributes(dwn->display, window, &wa)) { if (!XGetWindowAttributes(dwn->display, window, &wa)) {
LOG_WARN("client_manage: window %lu disappeared before reparenting", window); LOG_WARN("client_manage: window %lu disappeared before reparenting", window);
@ -179,9 +162,8 @@ Client *client_manage(Window window)
client_sync_log("client_manage: reparenting"); client_sync_log("client_manage: reparenting");
client_reparent_to_frame(client); client_reparent_to_frame(client);
/* Verify both window AND frame still exist after reparent */
client_sync_log("client_manage: verifying after reparent"); client_sync_log("client_manage: verifying after reparent");
XSync(dwn->display, False); /* Flush any pending errors */ XSync(dwn->display, False);
if (client->frame == None) { if (client->frame == None) {
client_sync_log("client_manage: frame is None after reparent"); client_sync_log("client_manage: frame is None after reparent");
@ -207,24 +189,19 @@ Client *client_manage(Window window)
} }
client_sync_log("client_manage: configuring"); client_sync_log("client_manage: configuring");
/* Resize client window to match frame dimensions */
client_configure(client); client_configure(client);
client_sync_log("client_manage: adding to list"); client_sync_log("client_manage: adding to list");
/* Add to client list */
client_add_to_list(client); client_add_to_list(client);
client_sync_log("client_manage: adding to workspace"); client_sync_log("client_manage: adding to workspace");
/* Add to current workspace */
workspace_add_client(dwn->current_workspace, client); workspace_add_client(dwn->current_workspace, client);
client_sync_log("client_manage: setting EWMH"); client_sync_log("client_manage: setting EWMH");
/* Set EWMH properties */
atoms_set_window_desktop(window, client->workspace); atoms_set_window_desktop(window, client->workspace);
atoms_update_client_list(); atoms_update_client_list();
client_sync_log("client_manage: verifying window again"); client_sync_log("client_manage: verifying window again");
/* Verify window still exists before subscribing to events */
if (!XGetWindowAttributes(dwn->display, window, &wa)) { if (!XGetWindowAttributes(dwn->display, window, &wa)) {
LOG_WARN("client_manage: window %lu disappeared before event setup", window); LOG_WARN("client_manage: window %lu disappeared before event setup", window);
client_sync_log("client_manage: window gone before events"); client_sync_log("client_manage: window gone before events");
@ -236,22 +213,18 @@ Client *client_manage(Window window)
} }
client_sync_log("client_manage: selecting input"); client_sync_log("client_manage: selecting input");
/* Subscribe to window events */
XSelectInput(dwn->display, window, XSelectInput(dwn->display, window,
EnterWindowMask | FocusChangeMask | PropertyChangeMask | EnterWindowMask | FocusChangeMask | PropertyChangeMask |
StructureNotifyMask); StructureNotifyMask);
client_sync_log("client_manage: grabbing button"); client_sync_log("client_manage: grabbing button");
/* Grab button for click-to-focus (will replay event to app after focusing) */
XGrabButton(dwn->display, Button1, AnyModifier, window, False, XGrabButton(dwn->display, Button1, AnyModifier, window, False,
ButtonPressMask, GrabModeSync, GrabModeAsync, None, None); ButtonPressMask, GrabModeSync, GrabModeAsync, None, None);
client_sync_log("client_manage: syncing X"); client_sync_log("client_manage: syncing X");
/* Sync to ensure all operations completed */
XSync(dwn->display, False); XSync(dwn->display, False);
client_sync_log("client_manage: showing"); client_sync_log("client_manage: showing");
/* Map and focus */
client_show(client); client_show(client);
client_sync_log("client_manage: focusing"); client_sync_log("client_manage: focusing");
@ -273,27 +246,21 @@ void client_unmanage(Client *client)
LOG_DEBUG("Unmanaging window: %lu", client->window); LOG_DEBUG("Unmanaging window: %lu", client->window);
client_sync_log("client_unmanage: remove from workspace"); client_sync_log("client_unmanage: remove from workspace");
/* Remove from workspace */
workspace_remove_client(client->workspace, client); workspace_remove_client(client->workspace, client);
client_sync_log("client_unmanage: remove from list"); client_sync_log("client_unmanage: remove from list");
/* Remove from client list */
client_remove_from_list(client); client_remove_from_list(client);
client_sync_log("client_unmanage: reparent from frame"); client_sync_log("client_unmanage: reparent from frame");
/* Reparent back to root */
client_reparent_from_frame(client); client_reparent_from_frame(client);
client_sync_log("client_unmanage: update EWMH"); client_sync_log("client_unmanage: update EWMH");
/* Update EWMH */
atoms_update_client_list(); atoms_update_client_list();
client_sync_log("client_unmanage: destroy client"); client_sync_log("client_unmanage: destroy client");
/* Destroy client */
client_destroy(client); client_destroy(client);
client_sync_log("client_unmanage: focus next"); client_sync_log("client_unmanage: focus next");
/* Focus next client if needed */
Workspace *ws = workspace_get_current(); Workspace *ws = workspace_get_current();
if (ws != NULL && ws->focused == NULL) { if (ws != NULL && ws->focused == NULL) {
Client *next = workspace_get_first_client(dwn->current_workspace); Client *next = workspace_get_first_client(dwn->current_workspace);
@ -333,7 +300,6 @@ Client *client_find_by_frame(Window frame)
return NULL; return NULL;
} }
/* ========== Client state ========== */
void client_focus(Client *client) void client_focus(Client *client)
{ {
@ -344,14 +310,12 @@ void client_focus(Client *client)
return; return;
} }
/* Defensive: verify window is still valid */
if (client->window == None) { if (client->window == None) {
LOG_WARN("client_focus: client has invalid window (None)"); LOG_WARN("client_focus: client has invalid window (None)");
client_sync_log("client_focus: window is None"); client_sync_log("client_focus: window is None");
return; return;
} }
/* Verify window still exists before focusing */
XWindowAttributes wa; XWindowAttributes wa;
if (!XGetWindowAttributes(dwn->display, client->window, &wa)) { if (!XGetWindowAttributes(dwn->display, client->window, &wa)) {
LOG_WARN("client_focus: window %lu no longer exists", client->window); LOG_WARN("client_focus: window %lu no longer exists", client->window);
@ -361,7 +325,6 @@ void client_focus(Client *client)
client_sync_log("client_focus: unfocusing previous"); client_sync_log("client_focus: unfocusing previous");
/* Unfocus previous */
Workspace *ws = workspace_get(client->workspace); Workspace *ws = workspace_get(client->workspace);
if (ws != NULL && ws->focused != NULL && ws->focused != client) { if (ws != NULL && ws->focused != NULL && ws->focused != client) {
client_unfocus(ws->focused); client_unfocus(ws->focused);
@ -369,30 +332,25 @@ void client_focus(Client *client)
client_sync_log("client_focus: XSetInputFocus"); client_sync_log("client_focus: XSetInputFocus");
/* Set focus with error handling */
XSetInputFocus(dwn->display, client->window, RevertToPointerRoot, CurrentTime); XSetInputFocus(dwn->display, client->window, RevertToPointerRoot, CurrentTime);
XSync(dwn->display, False); /* Catch any X errors immediately */ XSync(dwn->display, False);
client_sync_log("client_focus: updating workspace"); client_sync_log("client_focus: updating workspace");
/* Update workspace */
if (ws != NULL) { if (ws != NULL) {
ws->focused = client; ws->focused = client;
} }
client_sync_log("client_focus: raising"); client_sync_log("client_focus: raising");
/* Raise window */
client_raise(client); client_raise(client);
client_sync_log("client_focus: decorations"); client_sync_log("client_focus: decorations");
/* Update decorations */
decorations_render(client, true); decorations_render(client, true);
client_sync_log("client_focus: EWMH"); client_sync_log("client_focus: EWMH");
/* Update EWMH */
atoms_set_active_window(client->window); atoms_set_active_window(client->window);
client_sync_log("client_focus: DONE"); client_sync_log("client_focus: DONE");
@ -405,7 +363,6 @@ void client_unfocus(Client *client)
return; return;
} }
/* Update decorations */
decorations_render(client, false); decorations_render(client, false);
} }
@ -426,7 +383,6 @@ void client_raise(Client *client)
XRaiseWindow(dwn->display, win); XRaiseWindow(dwn->display, win);
/* Keep notifications on top */
notifications_raise_all(); notifications_raise_all();
} }
@ -468,7 +424,6 @@ void client_restore(Client *client)
client_focus(client); client_focus(client);
} }
/* ========== Client geometry ========== */
void client_move(Client *client, int x, int y) void client_move(Client *client, int x, int y)
{ {
@ -517,7 +472,6 @@ void client_configure(Client *client)
return; return;
} }
/* Verify window still exists before configuring */
XWindowAttributes wa; XWindowAttributes wa;
if (!XGetWindowAttributes(dwn->display, client->window, &wa)) { if (!XGetWindowAttributes(dwn->display, client->window, &wa)) {
LOG_DEBUG("client_configure: window no longer exists"); LOG_DEBUG("client_configure: window no longer exists");
@ -529,14 +483,12 @@ void client_configure(Client *client)
int border = client->border_width; int border = client->border_width;
if (client->frame != None) { if (client->frame != None) {
/* Move/resize frame */
XMoveResizeWindow(dwn->display, client->frame, XMoveResizeWindow(dwn->display, client->frame,
client->x - border, client->x - border,
client->y - title_height - border, client->y - title_height - border,
client->width + 2 * border, client->width + 2 * border,
client->height + title_height + 2 * border); client->height + title_height + 2 * border);
/* Move/resize client window within frame */
XMoveResizeWindow(dwn->display, client->window, XMoveResizeWindow(dwn->display, client->window,
border, title_height + border, border, title_height + border,
client->width, client->height); client->width, client->height);
@ -546,9 +498,8 @@ void client_configure(Client *client)
client->width, client->height); client->width, client->height);
} }
/* Send configure notify to client */
XConfigureEvent ce; XConfigureEvent ce;
memset(&ce, 0, sizeof(ce)); /* Zero-initialize */ memset(&ce, 0, sizeof(ce));
ce.type = ConfigureNotify; ce.type = ConfigureNotify;
ce.event = client->window; ce.event = client->window;
ce.window = client->window; ce.window = client->window;
@ -576,19 +527,16 @@ void client_apply_size_hints(Client *client, int *width, int *height)
return; return;
} }
/* Minimum size */
if (hints.flags & PMinSize) { if (hints.flags & PMinSize) {
if (*width < hints.min_width) *width = hints.min_width; if (*width < hints.min_width) *width = hints.min_width;
if (*height < hints.min_height) *height = hints.min_height; if (*height < hints.min_height) *height = hints.min_height;
} }
/* Maximum size */
if (hints.flags & PMaxSize) { if (hints.flags & PMaxSize) {
if (*width > hints.max_width) *width = hints.max_width; if (*width > hints.max_width) *width = hints.max_width;
if (*height > hints.max_height) *height = hints.max_height; if (*height > hints.max_height) *height = hints.max_height;
} }
/* Size increments */
if (hints.flags & PResizeInc) { if (hints.flags & PResizeInc) {
int base_w = (hints.flags & PBaseSize) ? hints.base_width : 0; int base_w = (hints.flags & PBaseSize) ? hints.base_width : 0;
int base_h = (hints.flags & PBaseSize) ? hints.base_height : 0; int base_h = (hints.flags & PBaseSize) ? hints.base_height : 0;
@ -598,7 +546,6 @@ void client_apply_size_hints(Client *client, int *width, int *height)
} }
} }
/* ========== Client properties ========== */
void client_update_title(Client *client) void client_update_title(Client *client)
{ {
@ -615,7 +562,6 @@ void client_update_title(Client *client)
strncpy(client->title, "Untitled", sizeof(client->title) - 1); strncpy(client->title, "Untitled", sizeof(client->title) - 1);
} }
/* Redraw decorations */
if (client->frame != None) { if (client->frame != None) {
Workspace *ws = workspace_get(client->workspace); Workspace *ws = workspace_get(client->workspace);
bool focused = (ws != NULL && ws->focused == client); bool focused = (ws != NULL && ws->focused == client);
@ -638,7 +584,6 @@ void client_set_fullscreen(Client *client, bool fullscreen)
return; return;
} }
/* Verify window still exists */
if (client->window == None) { if (client->window == None) {
return; return;
} }
@ -650,7 +595,6 @@ void client_set_fullscreen(Client *client, bool fullscreen)
} }
if (fullscreen) { if (fullscreen) {
/* Save current geometry */
if (!(client->flags & CLIENT_FULLSCREEN)) { if (!(client->flags & CLIENT_FULLSCREEN)) {
client->old_x = client->x; client->old_x = client->x;
client->old_y = client->y; client->old_y = client->y;
@ -660,13 +604,11 @@ void client_set_fullscreen(Client *client, bool fullscreen)
client->flags |= CLIENT_FULLSCREEN; client->flags |= CLIENT_FULLSCREEN;
/* Get monitor dimensions */
client->x = 0; client->x = 0;
client->y = 0; client->y = 0;
client->width = dwn->screen_width; client->width = dwn->screen_width;
client->height = dwn->screen_height; client->height = dwn->screen_height;
/* Reparent window to root and hide frame */
if (client->frame != None) { if (client->frame != None) {
XUnmapWindow(dwn->display, client->frame); XUnmapWindow(dwn->display, client->frame);
XSync(dwn->display, False); XSync(dwn->display, False);
@ -680,13 +622,11 @@ void client_set_fullscreen(Client *client, bool fullscreen)
} else { } else {
client->flags &= ~CLIENT_FULLSCREEN; client->flags &= ~CLIENT_FULLSCREEN;
/* Restore geometry */
client->x = client->old_x; client->x = client->old_x;
client->y = client->old_y; client->y = client->old_y;
client->width = client->old_width; client->width = client->old_width;
client->height = client->old_height; client->height = client->old_height;
/* Reparent back to frame and show it */
if (client->frame != None) { if (client->frame != None) {
int title_height = config_get_title_height(); int title_height = config_get_title_height();
int border = client->border_width; int border = client->border_width;
@ -729,7 +669,6 @@ void client_toggle_floating(Client *client)
client_set_floating(client, !(client->flags & CLIENT_FLOATING)); client_set_floating(client, !(client->flags & CLIENT_FLOATING));
} }
/* ========== Window type checking ========== */
bool client_is_floating(Client *client) bool client_is_floating(Client *client)
{ {
@ -753,7 +692,6 @@ bool client_is_dialog(Window window)
return type == ewmh.NET_WM_WINDOW_TYPE_DIALOG; return type == ewmh.NET_WM_WINDOW_TYPE_DIALOG;
} }
/* Check transient hint */
Window transient_for = None; Window transient_for = None;
if (XGetTransientForHint(dwn->display, window, &transient_for)) { if (XGetTransientForHint(dwn->display, window, &transient_for)) {
return transient_for != None; return transient_for != None;
@ -780,7 +718,6 @@ bool client_is_desktop(Window window)
return false; return false;
} }
/* ========== Frame management ========== */
void client_create_frame(Client *client) void client_create_frame(Client *client)
{ {
@ -793,7 +730,6 @@ void client_create_frame(Client *client)
return; return;
} }
/* Defensive: verify client window is valid */
if (client->window == None) { if (client->window == None) {
LOG_ERROR("client_create_frame: client has no valid window"); LOG_ERROR("client_create_frame: client has no valid window");
return; return;
@ -802,7 +738,6 @@ void client_create_frame(Client *client)
int title_height = config_get_title_height(); int title_height = config_get_title_height();
int border = client->border_width; int border = client->border_width;
/* Ensure reasonable dimensions */
int frame_width = client->width + 2 * border; int frame_width = client->width + 2 * border;
int frame_height = client->height + title_height + 2 * border; int frame_height = client->height + title_height + 2 * border;
if (frame_width <= 0 || frame_height <= 0) { if (frame_width <= 0 || frame_height <= 0) {
@ -810,9 +745,8 @@ void client_create_frame(Client *client)
return; return;
} }
/* Create frame window */
XSetWindowAttributes swa; XSetWindowAttributes swa;
memset(&swa, 0, sizeof(swa)); /* Defensive: zero-initialize */ memset(&swa, 0, sizeof(swa));
swa.override_redirect = True; swa.override_redirect = True;
swa.background_pixel = dwn->config->colors.title_unfocused_bg; swa.background_pixel = dwn->config->colors.title_unfocused_bg;
swa.border_pixel = dwn->config->colors.border_unfocused; swa.border_pixel = dwn->config->colors.border_unfocused;
@ -830,14 +764,13 @@ void client_create_frame(Client *client)
CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWEventMask, CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWEventMask,
&swa); &swa);
/* Verify frame was created successfully */
if (client->frame == None) { if (client->frame == None) {
LOG_ERROR("client_create_frame: XCreateWindow failed for window %lu", client->window); LOG_ERROR("client_create_frame: XCreateWindow failed for window %lu", client->window);
return; return;
} }
XSetWindowBorderWidth(dwn->display, client->frame, border); XSetWindowBorderWidth(dwn->display, client->frame, border);
XSync(dwn->display, False); /* Catch any X errors */ XSync(dwn->display, False);
LOG_DEBUG("Created frame %lu for window %lu", client->frame, client->window); LOG_DEBUG("Created frame %lu for window %lu", client->frame, client->window);
} }
@ -862,7 +795,6 @@ void client_reparent_to_frame(Client *client)
return; return;
} }
/* Verify window still exists before reparenting */
if (client->window == None) { if (client->window == None) {
LOG_WARN("client_reparent_to_frame: client has no valid window"); LOG_WARN("client_reparent_to_frame: client has no valid window");
return; return;
@ -877,16 +809,13 @@ void client_reparent_to_frame(Client *client)
int title_height = config_get_title_height(); int title_height = config_get_title_height();
int border = client->border_width; int border = client->border_width;
/* Sync before reparent to catch any pending errors */
XSync(dwn->display, False); XSync(dwn->display, False);
XReparentWindow(dwn->display, client->window, client->frame, XReparentWindow(dwn->display, client->window, client->frame,
border, title_height + border); border, title_height + border);
/* Sync after reparent to catch errors immediately */
XSync(dwn->display, False); XSync(dwn->display, False);
/* Save the frame window association */
XSaveContext(dwn->display, client->window, XUniqueContext(), (XPointer)client); XSaveContext(dwn->display, client->window, XUniqueContext(), (XPointer)client);
} }
@ -900,24 +829,20 @@ void client_reparent_from_frame(Client *client)
return; return;
} }
/* Verify window still exists before reparenting */
XWindowAttributes wa; XWindowAttributes wa;
if (!XGetWindowAttributes(dwn->display, client->window, &wa)) { if (!XGetWindowAttributes(dwn->display, client->window, &wa)) {
LOG_WARN("client_reparent_from_frame: window %lu no longer exists", client->window); LOG_WARN("client_reparent_from_frame: window %lu no longer exists", client->window);
return; return;
} }
/* Sync before reparent to catch any pending errors */
XSync(dwn->display, False); XSync(dwn->display, False);
XReparentWindow(dwn->display, client->window, dwn->root, XReparentWindow(dwn->display, client->window, dwn->root,
client->x, client->y); client->x, client->y);
/* Sync after reparent to catch errors immediately */
XSync(dwn->display, False); XSync(dwn->display, False);
} }
/* ========== Visibility ========== */
void client_show(Client *client) void client_show(Client *client)
{ {
@ -929,7 +854,6 @@ void client_show(Client *client)
return; return;
} }
/* Verify window still exists before mapping */
XWindowAttributes wa; XWindowAttributes wa;
if (!XGetWindowAttributes(dwn->display, client->window, &wa)) { if (!XGetWindowAttributes(dwn->display, client->window, &wa)) {
LOG_DEBUG("client_show: window no longer exists"); LOG_DEBUG("client_show: window no longer exists");
@ -952,7 +876,6 @@ void client_hide(Client *client)
return; return;
} }
/* Verify window still exists before unmapping */
XWindowAttributes wa; XWindowAttributes wa;
if (!XGetWindowAttributes(dwn->display, client->window, &wa)) { if (!XGetWindowAttributes(dwn->display, client->window, &wa)) {
LOG_DEBUG("client_hide: window no longer exists"); LOG_DEBUG("client_hide: window no longer exists");
@ -971,12 +894,10 @@ bool client_is_visible(Client *client)
return false; return false;
} }
/* Hidden windows are not visible */
if (client->flags & CLIENT_MINIMIZED) { if (client->flags & CLIENT_MINIMIZED) {
return false; return false;
} }
/* Only visible if on current workspace (or sticky) */
if (!(client->flags & CLIENT_STICKY) && if (!(client->flags & CLIENT_STICKY) &&
client->workspace != (unsigned int)dwn->current_workspace) { client->workspace != (unsigned int)dwn->current_workspace) {
return false; return false;
@ -985,7 +906,6 @@ bool client_is_visible(Client *client)
return true; return true;
} }
/* ========== Close handling ========== */
void client_close(Client *client) void client_close(Client *client)
{ {
@ -997,18 +917,15 @@ void client_close(Client *client)
return; return;
} }
/* Verify window still exists */
XWindowAttributes wa; XWindowAttributes wa;
if (!XGetWindowAttributes(dwn->display, client->window, &wa)) { if (!XGetWindowAttributes(dwn->display, client->window, &wa)) {
LOG_DEBUG("client_close: window no longer exists"); LOG_DEBUG("client_close: window no longer exists");
return; return;
} }
/* Try WM_DELETE_WINDOW first */
if (atoms_window_supports_protocol(client->window, icccm.WM_DELETE_WINDOW)) { if (atoms_window_supports_protocol(client->window, icccm.WM_DELETE_WINDOW)) {
atoms_send_protocol(client->window, icccm.WM_DELETE_WINDOW, CurrentTime); atoms_send_protocol(client->window, icccm.WM_DELETE_WINDOW, CurrentTime);
} else { } else {
/* Force kill */
client_kill(client); client_kill(client);
} }
} }
@ -1026,7 +943,6 @@ void client_kill(Client *client)
XKillClient(dwn->display, client->window); XKillClient(dwn->display, client->window);
} }
/* ========== List operations ========== */
void client_add_to_list(Client *client) void client_add_to_list(Client *client)
{ {
@ -1080,7 +996,6 @@ int client_count_on_workspace(int workspace)
return count; return count;
} }
/* ========== Iteration ========== */
Client *client_get_next(Client *client) Client *client_get_next(Client *client)
{ {

View File

@ -12,7 +12,6 @@
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
/* Default color values (dark modern theme) */
#define DEFAULT_PANEL_BG "#1a1a2e" #define DEFAULT_PANEL_BG "#1a1a2e"
#define DEFAULT_PANEL_FG "#e0e0e0" #define DEFAULT_PANEL_FG "#e0e0e0"
#define DEFAULT_WS_ACTIVE "#4a90d9" #define DEFAULT_WS_ACTIVE "#4a90d9"
@ -27,7 +26,6 @@
#define DEFAULT_NOTIFICATION_BG "#2a2a3e" #define DEFAULT_NOTIFICATION_BG "#2a2a3e"
#define DEFAULT_NOTIFICATION_FG "#ffffff" #define DEFAULT_NOTIFICATION_FG "#ffffff"
/* ========== Configuration creation/destruction ========== */
Config *config_create(void) Config *config_create(void)
{ {
@ -39,7 +37,6 @@ Config *config_create(void)
void config_destroy(Config *cfg) void config_destroy(Config *cfg)
{ {
if (cfg != NULL) { if (cfg != NULL) {
/* Securely wipe API keys before freeing */
secure_wipe(cfg->openrouter_api_key, sizeof(cfg->openrouter_api_key)); secure_wipe(cfg->openrouter_api_key, sizeof(cfg->openrouter_api_key));
secure_wipe(cfg->exa_api_key, sizeof(cfg->exa_api_key)); secure_wipe(cfg->exa_api_key, sizeof(cfg->exa_api_key));
dwn_free(cfg); dwn_free(cfg);
@ -52,41 +49,34 @@ void config_set_defaults(Config *cfg)
return; return;
} }
/* General */
strncpy(cfg->terminal, "xfce4-terminal", sizeof(cfg->terminal) - 1); strncpy(cfg->terminal, "xfce4-terminal", sizeof(cfg->terminal) - 1);
strncpy(cfg->launcher, "dmenu_run", sizeof(cfg->launcher) - 1); strncpy(cfg->launcher, "dmenu_run", sizeof(cfg->launcher) - 1);
strncpy(cfg->file_manager, "thunar", sizeof(cfg->file_manager) - 1); strncpy(cfg->file_manager, "thunar", sizeof(cfg->file_manager) - 1);
cfg->focus_mode = FOCUS_CLICK; cfg->focus_mode = FOCUS_CLICK;
cfg->show_decorations = true; cfg->show_decorations = true;
/* Appearance */
cfg->border_width = DEFAULT_BORDER_WIDTH; cfg->border_width = DEFAULT_BORDER_WIDTH;
cfg->title_height = DEFAULT_TITLE_HEIGHT; cfg->title_height = DEFAULT_TITLE_HEIGHT;
cfg->panel_height = DEFAULT_PANEL_HEIGHT; cfg->panel_height = DEFAULT_PANEL_HEIGHT;
cfg->gap = DEFAULT_GAP; cfg->gap = DEFAULT_GAP;
strncpy(cfg->font_name, "fixed", sizeof(cfg->font_name) - 1); strncpy(cfg->font_name, "fixed", sizeof(cfg->font_name) - 1);
/* Layout */
cfg->default_master_ratio = 0.55f; cfg->default_master_ratio = 0.55f;
cfg->default_master_count = 1; cfg->default_master_count = 1;
cfg->default_layout = LAYOUT_TILING; cfg->default_layout = LAYOUT_TILING;
/* Panels */
cfg->top_panel_enabled = true; cfg->top_panel_enabled = true;
cfg->bottom_panel_enabled = true; cfg->bottom_panel_enabled = true;
/* AI (disabled by default, enabled if API key found) */
cfg->openrouter_api_key[0] = '\0'; cfg->openrouter_api_key[0] = '\0';
cfg->exa_api_key[0] = '\0'; cfg->exa_api_key[0] = '\0';
strncpy(cfg->ai_model, "google/gemini-2.0-flash-exp:free", sizeof(cfg->ai_model) - 1); strncpy(cfg->ai_model, "google/gemini-2.0-flash-exp:free", sizeof(cfg->ai_model) - 1);
cfg->ai_enabled = false; cfg->ai_enabled = false;
/* Paths */
strncpy(cfg->config_path, "~/.config/dwn/config", sizeof(cfg->config_path) - 1); strncpy(cfg->config_path, "~/.config/dwn/config", sizeof(cfg->config_path) - 1);
strncpy(cfg->log_path, "~/.local/share/dwn/dwn.log", sizeof(cfg->log_path) - 1); strncpy(cfg->log_path, "~/.local/share/dwn/dwn.log", sizeof(cfg->log_path) - 1);
} }
/* ========== INI Parser ========== */
typedef struct { typedef struct {
Config *cfg; Config *cfg;
@ -181,12 +171,10 @@ bool config_parse_ini(const char *path, ConfigCallback callback, void *user_data
while (fgets(line, sizeof(line), f) != NULL) { while (fgets(line, sizeof(line), f) != NULL) {
char *trimmed = str_trim(line); char *trimmed = str_trim(line);
/* Skip empty lines and comments */
if (trimmed[0] == '\0' || trimmed[0] == '#' || trimmed[0] == ';') { if (trimmed[0] == '\0' || trimmed[0] == '#' || trimmed[0] == ';') {
continue; continue;
} }
/* Section header */
if (trimmed[0] == '[') { if (trimmed[0] == '[') {
char *end = strchr(trimmed, ']'); char *end = strchr(trimmed, ']');
if (end != NULL) { if (end != NULL) {
@ -196,14 +184,12 @@ bool config_parse_ini(const char *path, ConfigCallback callback, void *user_data
continue; continue;
} }
/* Key = value */
char *equals = strchr(trimmed, '='); char *equals = strchr(trimmed, '=');
if (equals != NULL) { if (equals != NULL) {
*equals = '\0'; *equals = '\0';
char *key = str_trim(trimmed); char *key = str_trim(trimmed);
char *value = str_trim(equals + 1); char *value = str_trim(equals + 1);
/* Remove quotes from value */
size_t vlen = strlen(value); size_t vlen = strlen(value);
if (vlen >= 2 && ((value[0] == '"' && value[vlen-1] == '"') || if (vlen >= 2 && ((value[0] == '"' && value[vlen-1] == '"') ||
(value[0] == '\'' && value[vlen-1] == '\''))) { (value[0] == '\'' && value[vlen-1] == '\''))) {
@ -219,7 +205,6 @@ bool config_parse_ini(const char *path, ConfigCallback callback, void *user_data
return true; return true;
} }
/* ========== Load configuration ========== */
bool config_load(Config *cfg, const char *path) bool config_load(Config *cfg, const char *path)
{ {
@ -227,16 +212,13 @@ bool config_load(Config *cfg, const char *path)
return false; return false;
} }
/* Set defaults first */
config_set_defaults(cfg); config_set_defaults(cfg);
/* Determine config path */
const char *config_path = path; const char *config_path = path;
if (config_path == NULL) { if (config_path == NULL) {
config_path = cfg->config_path; config_path = cfg->config_path;
} }
/* Parse config file first (environment variables override below) */
ParseContext ctx = { .cfg = cfg }; ParseContext ctx = { .cfg = cfg };
char *expanded = expand_path(config_path); char *expanded = expand_path(config_path);
@ -249,7 +231,6 @@ bool config_load(Config *cfg, const char *path)
dwn_free(expanded); dwn_free(expanded);
/* Check for API keys in environment (override config file) */
const char *openrouter_key = getenv("OPENROUTER_API_KEY"); const char *openrouter_key = getenv("OPENROUTER_API_KEY");
if (openrouter_key != NULL && openrouter_key[0] != '\0') { if (openrouter_key != NULL && openrouter_key[0] != '\0') {
strncpy(cfg->openrouter_api_key, openrouter_key, sizeof(cfg->openrouter_api_key) - 1); strncpy(cfg->openrouter_api_key, openrouter_key, sizeof(cfg->openrouter_api_key) - 1);
@ -262,7 +243,6 @@ bool config_load(Config *cfg, const char *path)
strncpy(cfg->exa_api_key, exa_key, sizeof(cfg->exa_api_key) - 1); strncpy(cfg->exa_api_key, exa_key, sizeof(cfg->exa_api_key) - 1);
} }
/* Log AI status */
if (cfg->ai_enabled) { if (cfg->ai_enabled) {
LOG_INFO("AI features enabled"); LOG_INFO("AI features enabled");
} }
@ -275,13 +255,11 @@ bool config_reload(Config *cfg)
if (cfg == NULL) { if (cfg == NULL) {
return false; return false;
} }
/* Securely wipe existing API keys before reloading */
secure_wipe(cfg->openrouter_api_key, sizeof(cfg->openrouter_api_key)); secure_wipe(cfg->openrouter_api_key, sizeof(cfg->openrouter_api_key));
secure_wipe(cfg->exa_api_key, sizeof(cfg->exa_api_key)); secure_wipe(cfg->exa_api_key, sizeof(cfg->exa_api_key));
return config_load(cfg, cfg->config_path); return config_load(cfg, cfg->config_path);
} }
/* ========== Getters ========== */
const char *config_get_terminal(void) const char *config_get_terminal(void)
{ {
@ -339,7 +317,6 @@ const ColorScheme *config_get_colors(void)
return NULL; return NULL;
} }
/* Initialize colors (must be called after display is open) */
void config_init_colors(Config *cfg) void config_init_colors(Config *cfg)
{ {
if (cfg == NULL || dwn == NULL || dwn->display == NULL) { if (cfg == NULL || dwn == NULL || dwn->display == NULL) {

View File

@ -13,14 +13,11 @@
#include <stdbool.h> #include <stdbool.h>
#include <X11/Xft/Xft.h> #include <X11/Xft/Xft.h>
/* Button dimensions */
#define BUTTON_SIZE 16 #define BUTTON_SIZE 16
#define BUTTON_PADDING 4 #define BUTTON_PADDING 4
/* Resize edge size */
#define RESIZE_EDGE 8 #define RESIZE_EDGE 8
/* ========== Initialization ========== */
void decorations_init(void) void decorations_init(void)
{ {
@ -29,10 +26,8 @@ void decorations_init(void)
void decorations_cleanup(void) void decorations_cleanup(void)
{ {
/* Nothing to clean up */
} }
/* ========== Rendering ========== */
void decorations_render(Client *client, bool focused) void decorations_render(Client *client, bool focused)
{ {
@ -64,25 +59,21 @@ void decorations_render_title_bar(Client *client, bool focused)
int title_height = config_get_title_height(); int title_height = config_get_title_height();
int border = client->border_width; int border = client->border_width;
/* Set colors based on focus */
unsigned long bg_color = focused ? colors->title_focused_bg : colors->title_unfocused_bg; unsigned long bg_color = focused ? colors->title_focused_bg : colors->title_unfocused_bg;
unsigned long fg_color = focused ? colors->title_focused_fg : colors->title_unfocused_fg; unsigned long fg_color = focused ? colors->title_focused_fg : colors->title_unfocused_fg;
/* Draw title bar background */
XSetForeground(dpy, dwn->gc, bg_color); XSetForeground(dpy, dwn->gc, bg_color);
XFillRectangle(dpy, client->frame, dwn->gc, XFillRectangle(dpy, client->frame, dwn->gc,
border, border, border, border,
client->width, title_height); client->width, title_height);
/* Draw title text */
if (client->title[0] != '\0' && dwn->xft_font != NULL) { if (client->title[0] != '\0' && dwn->xft_font != NULL) {
int text_y = border + (title_height + dwn->xft_font->ascent) / 2; int text_y = border + (title_height + dwn->xft_font->ascent) / 2;
int text_x = border + BUTTON_PADDING; int text_x = border + BUTTON_PADDING;
/* Truncate title if too long */
int max_width = client->width - 3 * (BUTTON_SIZE + BUTTON_PADDING) - 2 * BUTTON_PADDING; int max_width = client->width - 3 * (BUTTON_SIZE + BUTTON_PADDING) - 2 * BUTTON_PADDING;
char display_title[256]; char display_title[256];
strncpy(display_title, client->title, sizeof(display_title) - 4); /* Leave room for "..." */ strncpy(display_title, client->title, sizeof(display_title) - 4);
display_title[sizeof(display_title) - 4] = '\0'; display_title[sizeof(display_title) - 4] = '\0';
XGlyphInfo extents; XGlyphInfo extents;
@ -90,16 +81,14 @@ void decorations_render_title_bar(Client *client, bool focused)
(const FcChar8 *)display_title, strlen(display_title), &extents); (const FcChar8 *)display_title, strlen(display_title), &extents);
int text_width = extents.xOff; int text_width = extents.xOff;
/* Truncate UTF-8 aware: find valid UTF-8 boundary */
bool title_truncated = false; bool title_truncated = false;
while (text_width > max_width && strlen(display_title) > 3) { while (text_width > max_width && strlen(display_title) > 3) {
size_t len = strlen(display_title); size_t len = strlen(display_title);
/* Move back to find UTF-8 character boundary */
size_t cut = len - 1; size_t cut = len - 1;
while (cut > 0 && (display_title[cut] & 0xC0) == 0x80) { while (cut > 0 && (display_title[cut] & 0xC0) == 0x80) {
cut--; /* Skip continuation bytes */ cut--;
} }
if (cut > 0) cut--; /* Remove one more character for ellipsis space */ if (cut > 0) cut--;
while (cut > 0 && (display_title[cut] & 0xC0) == 0x80) { while (cut > 0 && (display_title[cut] & 0xC0) == 0x80) {
cut--; cut--;
} }
@ -114,7 +103,6 @@ void decorations_render_title_bar(Client *client, bool focused)
strncat(display_title, "...", sizeof(display_title) - strlen(display_title) - 1); strncat(display_title, "...", sizeof(display_title) - strlen(display_title) - 1);
} }
/* Draw with Xft for UTF-8 support */
XftDraw *xft_draw = XftDrawCreate(dpy, client->frame, XftDraw *xft_draw = XftDrawCreate(dpy, client->frame,
DefaultVisual(dpy, dwn->screen), DefaultVisual(dpy, dwn->screen),
dwn->colormap); dwn->colormap);
@ -155,7 +143,6 @@ void decorations_render_buttons(Client *client, bool focused)
int title_height = config_get_title_height(); int title_height = config_get_title_height();
int border = client->border_width; int border = client->border_width;
/* Button positions (right-aligned) */
int button_y = border + (title_height - BUTTON_SIZE) / 2; int button_y = border + (title_height - BUTTON_SIZE) / 2;
int close_x = border + client->width - BUTTON_SIZE - BUTTON_PADDING; int close_x = border + client->width - BUTTON_SIZE - BUTTON_PADDING;
int max_x = close_x - BUTTON_SIZE - BUTTON_PADDING; int max_x = close_x - BUTTON_SIZE - BUTTON_PADDING;
@ -163,10 +150,9 @@ void decorations_render_buttons(Client *client, bool focused)
unsigned long bg_color = focused ? colors->title_focused_bg : colors->title_unfocused_bg; unsigned long bg_color = focused ? colors->title_focused_bg : colors->title_unfocused_bg;
/* Close button (red X) */
XSetForeground(dpy, dwn->gc, bg_color); XSetForeground(dpy, dwn->gc, bg_color);
XFillRectangle(dpy, client->frame, dwn->gc, close_x, button_y, BUTTON_SIZE, BUTTON_SIZE); XFillRectangle(dpy, client->frame, dwn->gc, close_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
XSetForeground(dpy, dwn->gc, 0xcc4444); /* Red */ XSetForeground(dpy, dwn->gc, 0xcc4444);
XDrawLine(dpy, client->frame, dwn->gc, XDrawLine(dpy, client->frame, dwn->gc,
close_x + 3, button_y + 3, close_x + 3, button_y + 3,
close_x + BUTTON_SIZE - 4, button_y + BUTTON_SIZE - 4); close_x + BUTTON_SIZE - 4, button_y + BUTTON_SIZE - 4);
@ -174,18 +160,16 @@ void decorations_render_buttons(Client *client, bool focused)
close_x + BUTTON_SIZE - 4, button_y + 3, close_x + BUTTON_SIZE - 4, button_y + 3,
close_x + 3, button_y + BUTTON_SIZE - 4); close_x + 3, button_y + BUTTON_SIZE - 4);
/* Maximize button (square) */
XSetForeground(dpy, dwn->gc, bg_color); XSetForeground(dpy, dwn->gc, bg_color);
XFillRectangle(dpy, client->frame, dwn->gc, max_x, button_y, BUTTON_SIZE, BUTTON_SIZE); XFillRectangle(dpy, client->frame, dwn->gc, max_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
XSetForeground(dpy, dwn->gc, 0x44cc44); /* Green */ XSetForeground(dpy, dwn->gc, 0x44cc44);
XDrawRectangle(dpy, client->frame, dwn->gc, XDrawRectangle(dpy, client->frame, dwn->gc,
max_x + 3, button_y + 3, max_x + 3, button_y + 3,
BUTTON_SIZE - 7, BUTTON_SIZE - 7); BUTTON_SIZE - 7, BUTTON_SIZE - 7);
/* Minimize button (line) */
XSetForeground(dpy, dwn->gc, bg_color); XSetForeground(dpy, dwn->gc, bg_color);
XFillRectangle(dpy, client->frame, dwn->gc, min_x, button_y, BUTTON_SIZE, BUTTON_SIZE); XFillRectangle(dpy, client->frame, dwn->gc, min_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
XSetForeground(dpy, dwn->gc, 0xcccc44); /* Yellow */ XSetForeground(dpy, dwn->gc, 0xcccc44);
XDrawLine(dpy, client->frame, dwn->gc, XDrawLine(dpy, client->frame, dwn->gc,
min_x + 3, button_y + BUTTON_SIZE - 5, min_x + 3, button_y + BUTTON_SIZE - 5,
min_x + BUTTON_SIZE - 4, button_y + BUTTON_SIZE - 5); min_x + BUTTON_SIZE - 4, button_y + BUTTON_SIZE - 5);
@ -207,18 +191,16 @@ void decorations_render_border(Client *client, bool focused)
XSetWindowBorder(dwn->display, client->frame, border_color); XSetWindowBorder(dwn->display, client->frame, border_color);
} }
/* ========== Hit testing ========== */
ButtonType decorations_hit_test_button(Client *client, int x, int y) ButtonType decorations_hit_test_button(Client *client, int x, int y)
{ {
if (client == NULL) { if (client == NULL) {
return BUTTON_COUNT; /* No button hit */ return BUTTON_COUNT;
} }
int title_height = config_get_title_height(); int title_height = config_get_title_height();
int border = client->border_width; int border = client->border_width;
/* Check if in title bar area */
if (y < border || y > border + title_height) { if (y < border || y > border + title_height) {
return BUTTON_COUNT; return BUTTON_COUNT;
} }
@ -228,19 +210,16 @@ ButtonType decorations_hit_test_button(Client *client, int x, int y)
int max_x = close_x - BUTTON_SIZE - BUTTON_PADDING; int max_x = close_x - BUTTON_SIZE - BUTTON_PADDING;
int min_x = max_x - BUTTON_SIZE - BUTTON_PADDING; int min_x = max_x - BUTTON_SIZE - BUTTON_PADDING;
/* Check close button */
if (x >= close_x && x < close_x + BUTTON_SIZE && if (x >= close_x && x < close_x + BUTTON_SIZE &&
y >= button_y && y < button_y + BUTTON_SIZE) { y >= button_y && y < button_y + BUTTON_SIZE) {
return BUTTON_CLOSE; return BUTTON_CLOSE;
} }
/* Check maximize button */
if (x >= max_x && x < max_x + BUTTON_SIZE && if (x >= max_x && x < max_x + BUTTON_SIZE &&
y >= button_y && y < button_y + BUTTON_SIZE) { y >= button_y && y < button_y + BUTTON_SIZE) {
return BUTTON_MAXIMIZE; return BUTTON_MAXIMIZE;
} }
/* Check minimize button */
if (x >= min_x && x < min_x + BUTTON_SIZE && if (x >= min_x && x < min_x + BUTTON_SIZE &&
y >= button_y && y < button_y + BUTTON_SIZE) { y >= button_y && y < button_y + BUTTON_SIZE) {
return BUTTON_MINIMIZE; return BUTTON_MINIMIZE;
@ -258,7 +237,6 @@ bool decorations_hit_test_title_bar(Client *client, int x, int y)
int title_height = config_get_title_height(); int title_height = config_get_title_height();
int border = client->border_width; int border = client->border_width;
/* In title bar but not on a button */
if (y >= border && y < border + title_height) { if (y >= border && y < border + title_height) {
ButtonType btn = decorations_hit_test_button(client, x, y); ButtonType btn = decorations_hit_test_button(client, x, y);
return btn == BUTTON_COUNT; return btn == BUTTON_COUNT;
@ -280,7 +258,6 @@ bool decorations_hit_test_resize_area(Client *client, int x, int y, int *directi
*direction = 0; *direction = 0;
/* Check edges */
bool left = (x < RESIZE_EDGE); bool left = (x < RESIZE_EDGE);
bool right = (x > frame_width - RESIZE_EDGE); bool right = (x > frame_width - RESIZE_EDGE);
bool top = (y < RESIZE_EDGE); bool top = (y < RESIZE_EDGE);
@ -290,7 +267,6 @@ bool decorations_hit_test_resize_area(Client *client, int x, int y, int *directi
return false; return false;
} }
/* Encode direction as bitmask */
if (left) *direction |= 1; if (left) *direction |= 1;
if (right) *direction |= 2; if (right) *direction |= 2;
if (top) *direction |= 4; if (top) *direction |= 4;
@ -299,7 +275,6 @@ bool decorations_hit_test_resize_area(Client *client, int x, int y, int *directi
return true; return true;
} }
/* ========== Button actions ========== */
void decorations_button_press(Client *client, ButtonType button) void decorations_button_press(Client *client, ButtonType button)
{ {
@ -328,7 +303,6 @@ void decorations_button_press(Client *client, ButtonType button)
} }
} }
/* ========== Text rendering ========== */
void decorations_draw_text(Window window, GC gc, int x, int y, void decorations_draw_text(Window window, GC gc, int x, int y,
const char *text, unsigned long color) const char *text, unsigned long color)
@ -337,7 +311,6 @@ void decorations_draw_text(Window window, GC gc, int x, int y,
return; return;
} }
/* Use Xft for UTF-8 support */
if (dwn->xft_font != NULL) { if (dwn->xft_font != NULL) {
XftDraw *xft_draw = XftDrawCreate(dwn->display, window, XftDraw *xft_draw = XftDrawCreate(dwn->display, window,
DefaultVisual(dwn->display, dwn->screen), DefaultVisual(dwn->display, dwn->screen),
@ -363,7 +336,6 @@ void decorations_draw_text(Window window, GC gc, int x, int y,
} }
} }
/* Fallback to legacy X11 text */
XSetForeground(dwn->display, gc, color); XSetForeground(dwn->display, gc, color);
XDrawString(dwn->display, window, gc, x, y, text, strlen(text)); XDrawString(dwn->display, window, gc, x, y, text, strlen(text));
} }

View File

@ -22,7 +22,6 @@
static bool super_pressed = false; static bool super_pressed = false;
static bool super_used_in_combo = false; static bool super_used_in_combo = false;
/* Forward declarations for key callbacks */
void key_spawn_terminal(void); void key_spawn_terminal(void);
void key_spawn_launcher(void); void key_spawn_launcher(void);
void key_spawn_file_manager(void); void key_spawn_file_manager(void);
@ -69,11 +68,9 @@ void key_show_shortcuts(void);
void key_start_tutorial(void); void key_start_tutorial(void);
void key_screenshot(void); void key_screenshot(void);
/* Key bindings storage */
static KeyBinding bindings[MAX_KEYBINDINGS]; static KeyBinding bindings[MAX_KEYBINDINGS];
static int binding_count = 0; static int binding_count = 0;
/* ========== Tutorial System ========== */
typedef struct { typedef struct {
const char *title; const char *title;
@ -91,7 +88,6 @@ static const TutorialStep tutorial_steps[] = {
"(Super = Windows/Meta key)", "(Super = Windows/Meta key)",
MOD_SUPER, XK_t MOD_SUPER, XK_t
}, },
/* === Applications === */
{ {
"1/20: Open Terminal", "1/20: Open Terminal",
"The terminal is your command center.\n\n" "The terminal is your command center.\n\n"
@ -120,7 +116,6 @@ static const TutorialStep tutorial_steps[] = {
"", "",
MOD_SUPER, XK_b MOD_SUPER, XK_b
}, },
/* === Window Management === */
{ {
"5/20: Switch Windows", "5/20: Switch Windows",
"Cycle through open windows.\n\n" "Cycle through open windows.\n\n"
@ -156,7 +151,6 @@ static const TutorialStep tutorial_steps[] = {
"", "",
MOD_SUPER, XK_F9 MOD_SUPER, XK_F9
}, },
/* === Workspaces === */
{ {
"10/20: Next Workspace", "10/20: Next Workspace",
"Switch to the next virtual desktop.\n\n" "Switch to the next virtual desktop.\n\n"
@ -178,7 +172,6 @@ static const TutorialStep tutorial_steps[] = {
"Use F1-F9 for workspaces 1-9", "Use F1-F9 for workspaces 1-9",
0, XK_F1 0, XK_F1
}, },
/* === Layout === */
{ {
"13/20: Cycle Layout", "13/20: Cycle Layout",
"Switch between tiling, floating, monocle.\n\n" "Switch between tiling, floating, monocle.\n\n"
@ -207,7 +200,6 @@ static const TutorialStep tutorial_steps[] = {
"", "",
MOD_SUPER, XK_i MOD_SUPER, XK_i
}, },
/* === AI Features === */
{ {
"17/20: AI Context", "17/20: AI Context",
"Show AI analysis of your current task.\n\n" "Show AI analysis of your current task.\n\n"
@ -229,7 +221,6 @@ static const TutorialStep tutorial_steps[] = {
"Requires EXA_API_KEY", "Requires EXA_API_KEY",
MOD_SUPER | MOD_SHIFT, XK_e MOD_SUPER | MOD_SHIFT, XK_e
}, },
/* === Help & System === */
{ {
"20/20: Show Shortcuts", "20/20: Show Shortcuts",
"Display all keyboard shortcuts.\n\n" "Display all keyboard shortcuts.\n\n"
@ -266,7 +257,7 @@ void tutorial_start(void)
const TutorialStep *step = &tutorial_steps[0]; const TutorialStep *step = &tutorial_steps[0];
char msg[512]; char msg[512];
snprintf(msg, sizeof(msg), "%s\n\n%s", step->instruction, step->hint); snprintf(msg, sizeof(msg), "%s\n\n%s", step->instruction, step->hint);
notification_show("DWN Tutorial", step->title, msg, NULL, 0); /* No timeout */ notification_show("DWN Tutorial", step->title, msg, NULL, 0);
} }
void tutorial_stop(void) void tutorial_stop(void)
@ -301,14 +292,11 @@ void tutorial_check_key(unsigned int modifiers, KeySym keysym)
const TutorialStep *step = &tutorial_steps[tutorial_current_step]; const TutorialStep *step = &tutorial_steps[tutorial_current_step];
/* Check if the pressed key matches the expected key */
if (modifiers == step->modifiers && keysym == step->keysym) { if (modifiers == step->modifiers && keysym == step->keysym) {
/* Correct key! Move to next step */
tutorial_next_step(); tutorial_next_step();
} }
} }
/* ========== Initialization ========== */
void keys_init(void) void keys_init(void)
{ {
@ -336,10 +324,8 @@ void keys_grab_all(void)
Display *dpy = dwn->display; Display *dpy = dwn->display;
Window root = dwn->root; Window root = dwn->root;
/* Ungrab first to avoid errors */
XUngrabKey(dpy, AnyKey, AnyModifier, root); XUngrabKey(dpy, AnyKey, AnyModifier, root);
/* Grab all registered bindings */
for (int i = 0; i < binding_count; i++) { for (int i = 0; i < binding_count; i++) {
KeyCode code = XKeysymToKeycode(dpy, bindings[i].keysym); KeyCode code = XKeysymToKeycode(dpy, bindings[i].keysym);
if (code == 0) { if (code == 0) {
@ -347,11 +333,10 @@ void keys_grab_all(void)
continue; continue;
} }
/* Grab with and without NumLock/CapsLock */
unsigned int modifiers[] = { unsigned int modifiers[] = {
bindings[i].modifiers, bindings[i].modifiers,
bindings[i].modifiers | Mod2Mask, /* NumLock */ bindings[i].modifiers | Mod2Mask,
bindings[i].modifiers | LockMask, /* CapsLock */ bindings[i].modifiers | LockMask,
bindings[i].modifiers | Mod2Mask | LockMask bindings[i].modifiers | Mod2Mask | LockMask
}; };
@ -382,7 +367,6 @@ void keys_ungrab_all(void)
XUngrabKey(dwn->display, AnyKey, AnyModifier, dwn->root); XUngrabKey(dwn->display, AnyKey, AnyModifier, dwn->root);
} }
/* ========== Key binding registration ========== */
void keys_bind(unsigned int modifiers, KeySym keysym, void keys_bind(unsigned int modifiers, KeySym keysym,
KeyCallback callback, const char *description) KeyCallback callback, const char *description)
@ -404,7 +388,6 @@ void keys_unbind(unsigned int modifiers, KeySym keysym)
for (int i = 0; i < binding_count; i++) { for (int i = 0; i < binding_count; i++) {
if (bindings[i].modifiers == modifiers && if (bindings[i].modifiers == modifiers &&
bindings[i].keysym == keysym) { bindings[i].keysym == keysym) {
/* Shift remaining bindings */
memmove(&bindings[i], &bindings[i + 1], memmove(&bindings[i], &bindings[i + 1],
(binding_count - i - 1) * sizeof(KeyBinding)); (binding_count - i - 1) * sizeof(KeyBinding));
binding_count--; binding_count--;
@ -419,7 +402,6 @@ void keys_clear_all(void)
memset(bindings, 0, sizeof(bindings)); memset(bindings, 0, sizeof(bindings));
} }
/* ========== Key event handling ========== */
void keys_handle_press(XKeyEvent *ev) void keys_handle_press(XKeyEvent *ev)
{ {
@ -439,10 +421,8 @@ void keys_handle_press(XKeyEvent *ev)
super_used_in_combo = true; super_used_in_combo = true;
} }
/* Clean modifiers (remove NumLock, CapsLock) */
unsigned int clean_mask = ev->state & ~(Mod2Mask | LockMask); unsigned int clean_mask = ev->state & ~(Mod2Mask | LockMask);
/* Check tutorial progress */
if (tutorial_is_active()) { if (tutorial_is_active()) {
tutorial_check_key(clean_mask, keysym); tutorial_check_key(clean_mask, keysym);
} }
@ -476,45 +456,32 @@ void keys_handle_release(XKeyEvent *ev)
} }
} }
/* ========== Default key bindings (XFCE-style) ========== */
void keys_setup_defaults(void) void keys_setup_defaults(void)
{ {
/* ===== XFCE-style shortcuts ===== */
/* Terminal: Ctrl+Alt+T (XFCE default) */
keys_bind(MOD_CTRL | MOD_ALT, XK_t, key_spawn_terminal, "Spawn terminal"); keys_bind(MOD_CTRL | MOD_ALT, XK_t, key_spawn_terminal, "Spawn terminal");
/* Application finder: Alt+F2 (XFCE default) */
keys_bind(MOD_ALT, XK_F2, key_spawn_launcher, "Application finder"); keys_bind(MOD_ALT, XK_F2, key_spawn_launcher, "Application finder");
/* File manager: Super+E (XFCE default) */
keys_bind(MOD_SUPER, XK_e, key_spawn_file_manager, "File manager"); keys_bind(MOD_SUPER, XK_e, key_spawn_file_manager, "File manager");
/* Browser: Super+B */
keys_bind(MOD_SUPER, XK_b, key_spawn_browser, "Web browser"); keys_bind(MOD_SUPER, XK_b, key_spawn_browser, "Web browser");
/* Close window: Alt+F4 (XFCE default) */
keys_bind(MOD_ALT, XK_F4, key_close_window, "Close window"); keys_bind(MOD_ALT, XK_F4, key_close_window, "Close window");
/* Maximize toggle: Alt+F10 (XFCE default) */
keys_bind(MOD_ALT, XK_F10, key_toggle_maximize, "Toggle maximize"); keys_bind(MOD_ALT, XK_F10, key_toggle_maximize, "Toggle maximize");
/* Fullscreen: Alt+F11 (XFCE default) */
keys_bind(MOD_ALT, XK_F11, key_toggle_fullscreen, "Toggle fullscreen"); keys_bind(MOD_ALT, XK_F11, key_toggle_fullscreen, "Toggle fullscreen");
/* Cycle windows: Alt+Tab (XFCE default) */
keys_bind(MOD_ALT, XK_Tab, key_focus_next, "Cycle windows"); keys_bind(MOD_ALT, XK_Tab, key_focus_next, "Cycle windows");
keys_bind(MOD_ALT | MOD_SHIFT, XK_Tab, key_focus_prev, "Cycle windows reverse"); keys_bind(MOD_ALT | MOD_SHIFT, XK_Tab, key_focus_prev, "Cycle windows reverse");
/* Toggle floating: Super+F9 */
keys_bind(MOD_SUPER, XK_F9, key_toggle_floating, "Toggle floating"); keys_bind(MOD_SUPER, XK_F9, key_toggle_floating, "Toggle floating");
/* Next/Previous workspace: Ctrl+Alt+Right/Left (XFCE default) */
keys_bind(MOD_CTRL | MOD_ALT, XK_Right, key_workspace_next, "Next workspace"); keys_bind(MOD_CTRL | MOD_ALT, XK_Right, key_workspace_next, "Next workspace");
keys_bind(MOD_CTRL | MOD_ALT, XK_Left, key_workspace_prev, "Previous workspace"); keys_bind(MOD_CTRL | MOD_ALT, XK_Left, key_workspace_prev, "Previous workspace");
/* Switch to workspace: F1-F9 */
keys_bind(0, XK_F1, key_workspace_1, "Switch to workspace 1"); keys_bind(0, XK_F1, key_workspace_1, "Switch to workspace 1");
keys_bind(0, XK_F2, key_workspace_2, "Switch to workspace 2"); keys_bind(0, XK_F2, key_workspace_2, "Switch to workspace 2");
keys_bind(0, XK_F3, key_workspace_3, "Switch to workspace 3"); keys_bind(0, XK_F3, key_workspace_3, "Switch to workspace 3");
@ -525,7 +492,6 @@ void keys_setup_defaults(void)
keys_bind(0, XK_F8, key_workspace_8, "Switch to workspace 8"); keys_bind(0, XK_F8, key_workspace_8, "Switch to workspace 8");
keys_bind(0, XK_F9, key_workspace_9, "Switch to workspace 9"); keys_bind(0, XK_F9, key_workspace_9, "Switch to workspace 9");
/* Move to workspace: Shift+F1-F9 */
keys_bind(MOD_SHIFT, XK_F1, key_move_to_workspace_1, "Move to workspace 1"); keys_bind(MOD_SHIFT, XK_F1, key_move_to_workspace_1, "Move to workspace 1");
keys_bind(MOD_SHIFT, XK_F2, key_move_to_workspace_2, "Move to workspace 2"); keys_bind(MOD_SHIFT, XK_F2, key_move_to_workspace_2, "Move to workspace 2");
keys_bind(MOD_SHIFT, XK_F3, key_move_to_workspace_3, "Move to workspace 3"); keys_bind(MOD_SHIFT, XK_F3, key_move_to_workspace_3, "Move to workspace 3");
@ -536,48 +502,34 @@ void keys_setup_defaults(void)
keys_bind(MOD_SHIFT, XK_F8, key_move_to_workspace_8, "Move to workspace 8"); keys_bind(MOD_SHIFT, XK_F8, key_move_to_workspace_8, "Move to workspace 8");
keys_bind(MOD_SHIFT, XK_F9, key_move_to_workspace_9, "Move to workspace 9"); keys_bind(MOD_SHIFT, XK_F9, key_move_to_workspace_9, "Move to workspace 9");
/* ===== DWN-specific shortcuts (all use Super key) ===== */
/* Quit DWN: Super+BackSpace */
keys_bind(MOD_SUPER, XK_BackSpace, key_quit_dwn, "Quit DWN"); keys_bind(MOD_SUPER, XK_BackSpace, key_quit_dwn, "Quit DWN");
/* Cycle layout mode: Super+Space */
keys_bind(MOD_SUPER, XK_space, key_cycle_layout, "Cycle layout"); keys_bind(MOD_SUPER, XK_space, key_cycle_layout, "Cycle layout");
/* Master area adjustments: Super+H/L/I/D */
keys_bind(MOD_SUPER, XK_l, key_increase_master, "Increase master ratio"); 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_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_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_d, key_decrease_master_count, "Decrease master count");
/* AI: Super+A */
keys_bind(MOD_SUPER, XK_a, key_toggle_ai, "Toggle AI context"); keys_bind(MOD_SUPER, XK_a, key_toggle_ai, "Toggle AI context");
keys_bind(MOD_SUPER | MOD_SHIFT, XK_a, key_ai_command, "AI command"); keys_bind(MOD_SUPER | MOD_SHIFT, XK_a, key_ai_command, "AI command");
/* Exa semantic search: Super+Shift+E */
keys_bind(MOD_SUPER | MOD_SHIFT, XK_e, key_exa_search, "Exa web search"); keys_bind(MOD_SUPER | MOD_SHIFT, XK_e, key_exa_search, "Exa web search");
/* Show shortcuts: Super+S */
keys_bind(MOD_SUPER, XK_s, key_show_shortcuts, "Show shortcuts"); keys_bind(MOD_SUPER, XK_s, key_show_shortcuts, "Show shortcuts");
/* Tutorial: Super+T */
keys_bind(MOD_SUPER, XK_t, key_start_tutorial, "Start tutorial"); keys_bind(MOD_SUPER, XK_t, key_start_tutorial, "Start tutorial");
/* ===== News ticker navigation ===== */
/* Super+Down: Next news article */
keys_bind(MOD_SUPER, XK_Down, key_news_next, "Next news article"); keys_bind(MOD_SUPER, XK_Down, key_news_next, "Next news article");
/* Super+Up: Previous news article */
keys_bind(MOD_SUPER, XK_Up, key_news_prev, "Previous news article"); keys_bind(MOD_SUPER, XK_Up, key_news_prev, "Previous news article");
/* Super+Return: Open current news article */
keys_bind(MOD_SUPER, XK_Return, key_news_open, "Open news article"); keys_bind(MOD_SUPER, XK_Return, key_news_open, "Open news article");
/* Screenshot: Print Screen (XFCE default) */
keys_bind(0, XK_Print, key_screenshot, "Take screenshot"); keys_bind(0, XK_Print, key_screenshot, "Take screenshot");
} }
/* ========== Key binding callbacks ========== */
void key_spawn_terminal(void) void key_spawn_terminal(void)
{ {
@ -647,8 +599,6 @@ void key_toggle_maximize(void)
{ {
Workspace *ws = workspace_get_current(); Workspace *ws = workspace_get_current();
if (ws != NULL && ws->focused != NULL) { if (ws != NULL && ws->focused != NULL) {
/* Toggle between maximized and normal state */
/* For now, use fullscreen as maximize equivalent */
client_toggle_fullscreen(ws->focused); client_toggle_fullscreen(ws->focused);
} }
} }
@ -670,7 +620,7 @@ void key_workspace_next(void)
} }
int next = dwn->current_workspace + 1; int next = dwn->current_workspace + 1;
if (next >= MAX_WORKSPACES) { if (next >= MAX_WORKSPACES) {
next = 0; /* Wrap around */ next = 0;
} }
workspace_switch(next); workspace_switch(next);
} }
@ -682,12 +632,11 @@ void key_workspace_prev(void)
} }
int prev = dwn->current_workspace - 1; int prev = dwn->current_workspace - 1;
if (prev < 0) { if (prev < 0) {
prev = MAX_WORKSPACES - 1; /* Wrap around */ prev = MAX_WORKSPACES - 1;
} }
workspace_switch(prev); workspace_switch(prev);
} }
/* Workspace switching */
void key_workspace_1(void) { workspace_switch(0); } void key_workspace_1(void) { workspace_switch(0); }
void key_workspace_2(void) { workspace_switch(1); } void key_workspace_2(void) { workspace_switch(1); }
void key_workspace_3(void) { workspace_switch(2); } void key_workspace_3(void) { workspace_switch(2); }
@ -698,7 +647,6 @@ void key_workspace_7(void) { workspace_switch(6); }
void key_workspace_8(void) { workspace_switch(7); } void key_workspace_8(void) { workspace_switch(7); }
void key_workspace_9(void) { workspace_switch(8); } void key_workspace_9(void) { workspace_switch(8); }
/* Move to workspace */
static void move_focused_to_workspace(int ws) static void move_focused_to_workspace(int ws)
{ {
Workspace *current = workspace_get_current(); Workspace *current = workspace_get_current();
@ -717,7 +665,6 @@ void key_move_to_workspace_7(void) { move_focused_to_workspace(6); }
void key_move_to_workspace_8(void) { move_focused_to_workspace(7); } void key_move_to_workspace_8(void) { move_focused_to_workspace(7); }
void key_move_to_workspace_9(void) { move_focused_to_workspace(8); } void key_move_to_workspace_9(void) { move_focused_to_workspace(8); }
/* Master area adjustments */
void key_increase_master(void) void key_increase_master(void)
{ {
if (dwn == NULL) { if (dwn == NULL) {
@ -750,7 +697,6 @@ void key_decrease_master_count(void)
workspace_adjust_master_count(dwn->current_workspace, -1); workspace_adjust_master_count(dwn->current_workspace, -1);
} }
/* AI functions */
void key_toggle_ai(void) void key_toggle_ai(void)
{ {
if (dwn == NULL) { if (dwn == NULL) {
@ -764,7 +710,6 @@ void key_toggle_ai(void)
return; return;
} }
/* Update and show AI context */
ai_update_context(); ai_update_context();
const char *task = ai_analyze_task(); const char *task = ai_analyze_task();
const char *suggestion = ai_suggest_window(); const char *suggestion = ai_suggest_window();
@ -850,14 +795,12 @@ void key_show_shortcuts(void)
void key_start_tutorial(void) void key_start_tutorial(void)
{ {
if (tutorial_is_active()) { if (tutorial_is_active()) {
/* If already in tutorial, this acts as "next" */
tutorial_next_step(); tutorial_next_step();
} else { } else {
tutorial_start(); tutorial_start();
} }
} }
/* ========== News ticker callbacks ========== */
void key_news_next(void) void key_news_next(void)
{ {

View File

@ -10,7 +10,6 @@
#include "config.h" #include "config.h"
#include "util.h" #include "util.h"
/* Layout names and symbols */
static const char *layout_names[] = { static const char *layout_names[] = {
"Tiling", "Tiling",
"Floating", "Floating",
@ -23,7 +22,6 @@ static const char *layout_symbols[] = {
"[M]" "[M]"
}; };
/* ========== Main arrangement function ========== */
void layout_arrange(int workspace) void layout_arrange(int workspace)
{ {
@ -48,7 +46,6 @@ void layout_arrange(int workspace)
} }
} }
/* ========== Tiling layout ========== */
void layout_arrange_tiling(int workspace) void layout_arrange_tiling(int workspace)
{ {
@ -57,7 +54,6 @@ void layout_arrange_tiling(int workspace)
return; return;
} }
/* Get usable screen area (excluding panels) */
int area_x, area_y, area_width, area_height; int area_x, area_y, area_width, area_height;
layout_get_usable_area(&area_x, &area_y, &area_width, &area_height); layout_get_usable_area(&area_x, &area_y, &area_width, &area_height);
@ -65,7 +61,6 @@ void layout_arrange_tiling(int workspace)
int title_height = config_get_title_height(); int title_height = config_get_title_height();
int border = config_get_border_width(); int border = config_get_border_width();
/* Count tiled (non-floating) clients */
int n = layout_count_tiled_clients(workspace); int n = layout_count_tiled_clients(workspace);
if (n == 0) { if (n == 0) {
return; return;
@ -76,7 +71,6 @@ void layout_arrange_tiling(int workspace)
master_count = n; master_count = n;
} }
/* Calculate master area width */
int master_width; int master_width;
if (n <= master_count) { if (n <= master_count) {
master_width = area_width - 2 * gap; master_width = area_width - 2 * gap;
@ -86,7 +80,6 @@ void layout_arrange_tiling(int workspace)
int stack_width = area_width - master_width - 3 * gap; int stack_width = area_width - master_width - 3 * gap;
/* Arrange windows */
int i = 0; int i = 0;
int master_y = area_y + gap; int master_y = area_y + gap;
int stack_y = area_y + gap; int stack_y = area_y + gap;
@ -102,7 +95,6 @@ void layout_arrange_tiling(int workspace)
int x, y, w, h; int x, y, w, h;
if (i < master_count) { if (i < master_count) {
/* Master area */
int master_h = (area_height - 2 * gap - (master_count - 1) * gap) / master_count; int master_h = (area_height - 2 * gap - (master_count - 1) * gap) / master_count;
x = area_x + gap; x = area_x + gap;
@ -112,7 +104,6 @@ void layout_arrange_tiling(int workspace)
master_y += h + gap; master_y += h + gap;
} else { } else {
/* Stack area */
int stack_count = n - master_count; int stack_count = n - master_count;
int stack_h = (area_height - 2 * gap - (stack_count - 1) * gap) / stack_count; int stack_h = (area_height - 2 * gap - (stack_count - 1) * gap) / stack_count;
@ -124,7 +115,6 @@ void layout_arrange_tiling(int workspace)
stack_y += h + gap; stack_y += h + gap;
} }
/* Account for decorations */
int actual_h = h - title_height - 2 * border; int actual_h = h - title_height - 2 * border;
int actual_w = w - 2 * border; int actual_w = w - 2 * border;
@ -138,12 +128,9 @@ void layout_arrange_tiling(int workspace)
} }
} }
/* ========== Floating layout ========== */
void layout_arrange_floating(int workspace) void layout_arrange_floating(int workspace)
{ {
/* In floating mode, we don't rearrange windows automatically.
Just make sure all clients are configured properly. */
for (Client *c = dwn->client_list; c != NULL; c = c->next) { for (Client *c = dwn->client_list; c != NULL; c = c->next) {
if (c->workspace != (unsigned int)workspace) { if (c->workspace != (unsigned int)workspace) {
@ -153,7 +140,6 @@ void layout_arrange_floating(int workspace)
continue; continue;
} }
/* Ensure window is within screen bounds */
int area_x, area_y, area_width, area_height; int area_x, area_y, area_width, area_height;
layout_get_usable_area(&area_x, &area_y, &area_width, &area_height); layout_get_usable_area(&area_x, &area_y, &area_width, &area_height);
@ -170,11 +156,9 @@ void layout_arrange_floating(int workspace)
} }
} }
/* ========== Monocle layout ========== */
void layout_arrange_monocle(int workspace) void layout_arrange_monocle(int workspace)
{ {
/* Get usable screen area */
int area_x, area_y, area_width, area_height; int area_x, area_y, area_width, area_height;
layout_get_usable_area(&area_x, &area_y, &area_width, &area_height); layout_get_usable_area(&area_x, &area_y, &area_width, &area_height);
@ -190,7 +174,6 @@ void layout_arrange_monocle(int workspace)
continue; continue;
} }
/* All tiled windows take the full usable area */
int x = area_x + gap; int x = area_x + gap;
int y = area_y + gap; int y = area_y + gap;
int w = area_width - 2 * gap - 2 * border; int w = area_width - 2 * gap - 2 * border;
@ -203,7 +186,6 @@ void layout_arrange_monocle(int workspace)
} }
} }
/* ========== Helpers ========== */
int layout_get_usable_area(int *x, int *y, int *width, int *height) int layout_get_usable_area(int *x, int *y, int *width, int *height)
{ {

View File

@ -30,11 +30,9 @@
#include <X11/cursorfont.h> #include <X11/cursorfont.h>
#include <X11/extensions/Xinerama.h> #include <X11/extensions/Xinerama.h>
/* Global state instance */
DWNState *dwn = NULL; DWNState *dwn = NULL;
static DWNState dwn_state; static DWNState dwn_state;
/* Signal handling */
static volatile sig_atomic_t received_signal = 0; static volatile sig_atomic_t received_signal = 0;
static void signal_handler(int sig) static void signal_handler(int sig)
@ -42,29 +40,23 @@ static void signal_handler(int sig)
received_signal = sig; received_signal = sig;
} }
/* Crash signal handler - flush logs before dying */
static void crash_signal_handler(int sig) static void crash_signal_handler(int sig)
{ {
/* Flush logs synchronously before crashing */
log_flush(); log_flush();
/* Re-raise the signal with default handler to get proper crash behavior */
signal(sig, SIG_DFL); signal(sig, SIG_DFL);
raise(sig); raise(sig);
} }
/* X11 error handlers */ static int last_x_error = 0;
static int last_x_error = 0; /* Track last error for checking */
static int x_error_handler(Display *dpy, XErrorEvent *ev) static int x_error_handler(Display *dpy, XErrorEvent *ev)
{ {
char error_text[256]; char error_text[256];
XGetErrorText(dpy, ev->error_code, error_text, sizeof(error_text)); XGetErrorText(dpy, ev->error_code, error_text, sizeof(error_text));
/* Store last error code for functions that want to check */
last_x_error = ev->error_code; last_x_error = ev->error_code;
/* Write all X errors to crash log for debugging */
FILE *crash = fopen("/tmp/dwn_crash.log", "a"); FILE *crash = fopen("/tmp/dwn_crash.log", "a");
if (crash) { if (crash) {
fprintf(crash, "[X_ERROR] code=%d request=%d resource=%lu: %s\n", fprintf(crash, "[X_ERROR] code=%d request=%d resource=%lu: %s\n",
@ -74,22 +66,19 @@ static int x_error_handler(Display *dpy, XErrorEvent *ev)
fclose(crash); fclose(crash);
} }
/* BadWindow errors are common and recoverable - just log and continue */
if (ev->error_code == BadWindow) { if (ev->error_code == BadWindow) {
LOG_DEBUG("X11 BadWindow error (request %d, resource %lu) - window was destroyed", LOG_DEBUG("X11 BadWindow error (request %d, resource %lu) - window was destroyed",
ev->request_code, ev->resourceid); ev->request_code, ev->resourceid);
return 0; /* Continue - this is not fatal */ return 0;
} }
/* BadMatch, BadValue, BadDrawable are also often recoverable */
if (ev->error_code == BadMatch || ev->error_code == BadValue || if (ev->error_code == BadMatch || ev->error_code == BadValue ||
ev->error_code == BadDrawable || ev->error_code == BadPixmap) { ev->error_code == BadDrawable || ev->error_code == BadPixmap) {
LOG_WARN("X11 error: %s (request %d, resource %lu) - continuing", LOG_WARN("X11 error: %s (request %d, resource %lu) - continuing",
error_text, ev->request_code, ev->resourceid); error_text, ev->request_code, ev->resourceid);
return 0; /* Continue - these are recoverable */ return 0;
} }
/* Log other errors but don't crash */
LOG_WARN("X11 error: %s (request %d, resource %lu)", LOG_WARN("X11 error: %s (request %d, resource %lu)",
error_text, ev->request_code, ev->resourceid); error_text, ev->request_code, ev->resourceid);
return 0; return 0;
@ -99,7 +88,6 @@ static int x_io_error_handler(Display *dpy)
{ {
(void)dpy; (void)dpy;
/* Write directly to crash log - do not rely on async logging */
FILE *crash = fopen("/tmp/dwn_crash.log", "a"); FILE *crash = fopen("/tmp/dwn_crash.log", "a");
if (crash) { if (crash) {
fprintf(crash, "[IO_ERROR] Fatal X11 I/O error - X server connection lost\n"); fprintf(crash, "[IO_ERROR] Fatal X11 I/O error - X server connection lost\n");
@ -110,16 +98,13 @@ static int x_io_error_handler(Display *dpy)
fprintf(stderr, "[IO_ERROR] Fatal X11 I/O error - X server connection lost\n"); fprintf(stderr, "[IO_ERROR] Fatal X11 I/O error - X server connection lost\n");
fflush(stderr); fflush(stderr);
/* I/O errors mean the X server connection is broken - we must exit */
/* But first, try to flush any pending logs */
log_flush(); log_flush();
LOG_ERROR("Fatal X11 I/O error - X server connection lost"); LOG_ERROR("Fatal X11 I/O error - X server connection lost");
log_flush(); log_flush();
_exit(EXIT_FAILURE); /* Use _exit to avoid cleanup that might touch X */ _exit(EXIT_FAILURE);
return 0; /* Never reached */ return 0;
} }
/* Check if another WM is running */
static int wm_detected = 0; static int wm_detected = 0;
static int wm_detect_error_handler(Display *dpy, XErrorEvent *ev) static int wm_detect_error_handler(Display *dpy, XErrorEvent *ev)
@ -143,11 +128,9 @@ static bool check_other_wm(void)
return wm_detected != 0; return wm_detected != 0;
} }
/* Initialize multi-monitor support */
static void init_monitors(void) static void init_monitors(void)
{ {
if (!XineramaIsActive(dwn->display)) { if (!XineramaIsActive(dwn->display)) {
/* Single monitor */
dwn->monitors[0].x = 0; dwn->monitors[0].x = 0;
dwn->monitors[0].y = 0; dwn->monitors[0].y = 0;
dwn->monitors[0].width = dwn->screen_width; dwn->monitors[0].width = dwn->screen_width;
@ -188,7 +171,6 @@ static void init_monitors(void)
LOG_INFO("Detected %d monitor(s)", dwn->monitor_count); LOG_INFO("Detected %d monitor(s)", dwn->monitor_count);
} }
/* Scan for existing windows */
static void scan_existing_windows(void) static void scan_existing_windows(void)
{ {
Window root_return, parent_return; Window root_return, parent_return;
@ -213,11 +195,9 @@ static void scan_existing_windows(void)
LOG_INFO("Scanned %d existing window(s)", client_count()); LOG_INFO("Scanned %d existing window(s)", client_count());
} }
/* ========== Event handlers ========== */
static void handle_map_request(XMapRequestEvent *ev) static void handle_map_request(XMapRequestEvent *ev)
{ {
/* Defensive: validate pointers */
if (ev == NULL || dwn == NULL || dwn->display == NULL) { if (ev == NULL || dwn == NULL || dwn->display == NULL) {
return; return;
} }
@ -242,7 +222,6 @@ static void handle_map_request(XMapRequestEvent *ev)
static void handle_unmap_notify(XUnmapEvent *ev) static void handle_unmap_notify(XUnmapEvent *ev)
{ {
/* Defensive: validate pointers */
if (ev == NULL || dwn == NULL) { if (ev == NULL || dwn == NULL) {
return; return;
} }
@ -252,15 +231,10 @@ static void handle_unmap_notify(XUnmapEvent *ev)
return; return;
} }
/* Ignore synthetic events */
if (ev->send_event) { if (ev->send_event) {
return; return;
} }
/* Don't unmanage windows that are intentionally hidden:
* - Windows on a different workspace (hidden during workspace switch)
* - Minimized windows
* These windows are still managed, just not visible */
if (c->workspace != (unsigned int)dwn->current_workspace) { if (c->workspace != (unsigned int)dwn->current_workspace) {
return; return;
} }
@ -268,13 +242,11 @@ static void handle_unmap_notify(XUnmapEvent *ev)
return; return;
} }
/* Window was actually closed/withdrawn - unmanage it */
client_unmanage(c); client_unmanage(c);
} }
static void handle_destroy_notify(XDestroyWindowEvent *ev) static void handle_destroy_notify(XDestroyWindowEvent *ev)
{ {
/* Defensive: validate pointers */
if (ev == NULL) { if (ev == NULL) {
return; return;
} }
@ -287,7 +259,6 @@ static void handle_destroy_notify(XDestroyWindowEvent *ev)
static void handle_configure_request(XConfigureRequestEvent *ev) static void handle_configure_request(XConfigureRequestEvent *ev)
{ {
/* Defensive: validate pointers */
if (ev == NULL || dwn == NULL || dwn->display == NULL) { if (ev == NULL || dwn == NULL || dwn->display == NULL) {
return; return;
} }
@ -295,7 +266,6 @@ static void handle_configure_request(XConfigureRequestEvent *ev)
Client *c = client_find_by_window(ev->window); Client *c = client_find_by_window(ev->window);
if (c != NULL) { if (c != NULL) {
/* Managed window - respect some requests for floating windows */
if (client_is_floating(c) || client_is_fullscreen(c)) { if (client_is_floating(c) || client_is_fullscreen(c)) {
if (ev->value_mask & CWX) c->x = ev->x; if (ev->value_mask & CWX) c->x = ev->x;
if (ev->value_mask & CWY) c->y = ev->y; if (ev->value_mask & CWY) c->y = ev->y;
@ -303,11 +273,9 @@ static void handle_configure_request(XConfigureRequestEvent *ev)
if (ev->value_mask & CWHeight) c->height = ev->height; if (ev->value_mask & CWHeight) c->height = ev->height;
client_configure(c); client_configure(c);
} else { } else {
/* Just send configure notify with current geometry */
client_configure(c); client_configure(c);
} }
} else { } else {
/* Unmanaged window - pass through */
XWindowChanges wc; XWindowChanges wc;
wc.x = ev->x; wc.x = ev->x;
wc.y = ev->y; wc.y = ev->y;
@ -323,7 +291,6 @@ static void handle_configure_request(XConfigureRequestEvent *ev)
static void handle_property_notify(XPropertyEvent *ev) static void handle_property_notify(XPropertyEvent *ev)
{ {
/* Defensive: validate pointers */
if (ev == NULL || dwn == NULL) { if (ev == NULL || dwn == NULL) {
return; return;
} }
@ -341,17 +308,14 @@ static void handle_property_notify(XPropertyEvent *ev)
static void handle_expose(XExposeEvent *ev) static void handle_expose(XExposeEvent *ev)
{ {
/* Defensive: validate pointers */
if (ev == NULL || dwn == NULL) { if (ev == NULL || dwn == NULL) {
return; return;
} }
/* Only handle final expose in a sequence */
if (ev->count != 0) { if (ev->count != 0) {
return; return;
} }
/* Check if it's a panel */
if (dwn->top_panel != NULL && ev->window == dwn->top_panel->window) { if (dwn->top_panel != NULL && ev->window == dwn->top_panel->window) {
panel_render(dwn->top_panel); panel_render(dwn->top_panel);
return; return;
@ -361,14 +325,12 @@ static void handle_expose(XExposeEvent *ev)
return; return;
} }
/* Check if it's a notification */
Notification *notif = notification_find_by_window(ev->window); Notification *notif = notification_find_by_window(ev->window);
if (notif != NULL) { if (notif != NULL) {
notification_render(notif); notification_render(notif);
return; return;
} }
/* Check if it's a frame */
Client *c = client_find_by_frame(ev->window); Client *c = client_find_by_frame(ev->window);
if (c != NULL) { if (c != NULL) {
Workspace *ws = workspace_get(c->workspace); Workspace *ws = workspace_get(c->workspace);
@ -379,7 +341,6 @@ static void handle_expose(XExposeEvent *ev)
static void handle_enter_notify(XCrossingEvent *ev) static void handle_enter_notify(XCrossingEvent *ev)
{ {
/* Defensive: validate all pointers */
if (ev == NULL || dwn == NULL || dwn->config == NULL) { if (ev == NULL || dwn == NULL || dwn->config == NULL) {
return; return;
} }
@ -388,7 +349,6 @@ static void handle_enter_notify(XCrossingEvent *ev)
return; return;
} }
/* Focus on enter for follow-mouse mode */
Client *c = client_find_by_frame(ev->window); Client *c = client_find_by_frame(ev->window);
if (c == NULL) { if (c == NULL) {
c = client_find_by_window(ev->window); c = client_find_by_window(ev->window);
@ -401,41 +361,34 @@ static void handle_enter_notify(XCrossingEvent *ev)
static void handle_button_press(XButtonEvent *ev) static void handle_button_press(XButtonEvent *ev)
{ {
/* Defensive: validate all pointers */
if (ev == NULL || dwn == NULL || dwn->display == NULL) { if (ev == NULL || dwn == NULL || dwn->display == NULL) {
return; return;
} }
/* Check volume slider first */
if (volume_slider != NULL && volume_slider->visible) { if (volume_slider != NULL && volume_slider->visible) {
if (ev->window == volume_slider->window) { if (ev->window == volume_slider->window) {
volume_slider_handle_click(volume_slider, ev->x, ev->y); volume_slider_handle_click(volume_slider, ev->x, ev->y);
return; return;
} else { } else {
/* Clicked outside slider - close it */
volume_slider_hide(volume_slider); volume_slider_hide(volume_slider);
} }
} }
/* Check WiFi dropdown menu */
if (wifi_menu != NULL && wifi_menu->visible) { if (wifi_menu != NULL && wifi_menu->visible) {
if (ev->window == wifi_menu->window) { if (ev->window == wifi_menu->window) {
dropdown_handle_click(wifi_menu, ev->x, ev->y); dropdown_handle_click(wifi_menu, ev->x, ev->y);
return; return;
} else { } else {
/* Clicked outside dropdown - close it */
dropdown_hide(wifi_menu); dropdown_hide(wifi_menu);
} }
} }
/* Check notifications first - clicking dismisses them */
Notification *notif = notification_find_by_window(ev->window); Notification *notif = notification_find_by_window(ev->window);
if (notif != NULL) { if (notif != NULL) {
notification_close(notif->id); notification_close(notif->id);
return; return;
} }
/* Check panels first */
if (dwn->top_panel != NULL && ev->window == dwn->top_panel->window) { if (dwn->top_panel != NULL && ev->window == dwn->top_panel->window) {
panel_handle_click(dwn->top_panel, ev->x, ev->y, ev->button); panel_handle_click(dwn->top_panel, ev->x, ev->y, ev->button);
return; return;
@ -445,7 +398,6 @@ static void handle_button_press(XButtonEvent *ev)
return; return;
} }
/* Find client */
Client *c = client_find_by_frame(ev->window); Client *c = client_find_by_frame(ev->window);
bool is_client_window = false; bool is_client_window = false;
if (c == NULL) { if (c == NULL) {
@ -457,20 +409,14 @@ static void handle_button_press(XButtonEvent *ev)
return; return;
} }
/* If click was on client window content, replay the event to the application FIRST
* before any other X operations. The synchronous grab freezes pointer events until
* XAllowEvents is called. Calling XAllowEvents before client_focus ensures the
* click reaches the application (tabs, buttons, etc.) without timing issues. */
if (is_client_window) { if (is_client_window) {
XAllowEvents(dwn->display, ReplayPointer, ev->time); XAllowEvents(dwn->display, ReplayPointer, ev->time);
client_focus(c); client_focus(c);
return; return;
} }
/* Focus on click */
client_focus(c); client_focus(c);
/* Check for button clicks in decorations */
if (c->frame != None && ev->window == c->frame) { if (c->frame != None && ev->window == c->frame) {
ButtonType btn = decorations_hit_test_button(c, ev->x, ev->y); ButtonType btn = decorations_hit_test_button(c, ev->x, ev->y);
if (btn != BUTTON_COUNT) { if (btn != BUTTON_COUNT) {
@ -478,10 +424,8 @@ static void handle_button_press(XButtonEvent *ev)
return; return;
} }
/* Check for title bar drag */
if (decorations_hit_test_title_bar(c, ev->x, ev->y)) { if (decorations_hit_test_title_bar(c, ev->x, ev->y)) {
if (ev->button == 1) { if (ev->button == 1) {
/* Start move */
dwn->drag_client = c; dwn->drag_client = c;
dwn->drag_start_x = ev->x_root; dwn->drag_start_x = ev->x_root;
dwn->drag_start_y = ev->y_root; dwn->drag_start_y = ev->y_root;
@ -496,7 +440,6 @@ static void handle_button_press(XButtonEvent *ev)
return; return;
} }
/* Check for resize */
int direction; int direction;
if (decorations_hit_test_resize_area(c, ev->x, ev->y, &direction)) { if (decorations_hit_test_resize_area(c, ev->x, ev->y, &direction)) {
if (ev->button == 1) { if (ev->button == 1) {
@ -519,12 +462,10 @@ static void handle_button_press(XButtonEvent *ev)
static void handle_button_release(XButtonEvent *ev) static void handle_button_release(XButtonEvent *ev)
{ {
/* Defensive: validate pointers */
if (dwn == NULL || dwn->display == NULL) { if (dwn == NULL || dwn->display == NULL) {
return; return;
} }
/* Handle volume slider release */
if (volume_slider != NULL && volume_slider->visible && volume_slider->dragging) { if (volume_slider != NULL && volume_slider->visible && volume_slider->dragging) {
volume_slider_handle_release(volume_slider); volume_slider_handle_release(volume_slider);
} }
@ -539,18 +480,15 @@ static void handle_button_release(XButtonEvent *ev)
static void handle_motion_notify(XMotionEvent *ev) static void handle_motion_notify(XMotionEvent *ev)
{ {
/* Defensive: validate pointers */
if (ev == NULL || dwn == NULL || dwn->display == NULL) { if (ev == NULL || dwn == NULL || dwn->display == NULL) {
return; return;
} }
/* Check volume slider drag */
if (volume_slider != NULL && volume_slider->visible && ev->window == volume_slider->window) { if (volume_slider != NULL && volume_slider->visible && ev->window == volume_slider->window) {
volume_slider_handle_motion(volume_slider, ev->x, ev->y); volume_slider_handle_motion(volume_slider, ev->x, ev->y);
return; return;
} }
/* Check WiFi dropdown hover */
if (wifi_menu != NULL && wifi_menu->visible && ev->window == wifi_menu->window) { if (wifi_menu != NULL && wifi_menu->visible && ev->window == wifi_menu->window) {
dropdown_handle_motion(wifi_menu, ev->x, ev->y); dropdown_handle_motion(wifi_menu, ev->x, ev->y);
return; return;
@ -562,7 +500,7 @@ static void handle_motion_notify(XMotionEvent *ev)
Client *c = dwn->drag_client; Client *c = dwn->drag_client;
if (c == NULL) { if (c == NULL) {
dwn->drag_client = NULL; /* Reset invalid drag state */ dwn->drag_client = NULL;
return; return;
} }
@ -582,7 +520,6 @@ static void handle_motion_notify(XMotionEvent *ev)
static void handle_client_message(XClientMessageEvent *ev) static void handle_client_message(XClientMessageEvent *ev)
{ {
/* Defensive: validate pointers */
if (ev == NULL || dwn == NULL || dwn->display == NULL) { if (ev == NULL || dwn == NULL || dwn->display == NULL) {
return; return;
} }
@ -626,7 +563,6 @@ static void handle_client_message(XClientMessageEvent *ev)
} }
} }
/* Sync log - disabled in production (enable for debugging crashes) */
#if 0 #if 0
static FILE *crash_log_file = NULL; static FILE *crash_log_file = NULL;
@ -645,16 +581,13 @@ static void sync_log(const char *msg)
} }
#endif #endif
/* Main event dispatcher */
void dwn_handle_event(XEvent *ev) void dwn_handle_event(XEvent *ev)
{ {
/* Defensive: validate event pointer */
if (ev == NULL) { if (ev == NULL) {
LOG_WARN("dwn_handle_event: received NULL event"); LOG_WARN("dwn_handle_event: received NULL event");
return; return;
} }
/* Defensive: validate global state */
if (dwn == NULL || dwn->display == NULL) { if (dwn == NULL || dwn->display == NULL) {
LOG_ERROR("dwn_handle_event: dwn or display is NULL"); LOG_ERROR("dwn_handle_event: dwn or display is NULL");
return; return;
@ -705,15 +638,12 @@ void dwn_handle_event(XEvent *ev)
} }
} }
/* ========== Core functions ========== */
int dwn_init(void) int dwn_init(void)
{ {
/* Initialize global state */
dwn = &dwn_state; dwn = &dwn_state;
memset(dwn, 0, sizeof(DWNState)); memset(dwn, 0, sizeof(DWNState));
/* Open display */
dwn->display = XOpenDisplay(NULL); dwn->display = XOpenDisplay(NULL);
if (dwn->display == NULL) { if (dwn->display == NULL) {
fprintf(stderr, "Cannot open X display\n"); fprintf(stderr, "Cannot open X display\n");
@ -726,36 +656,29 @@ int dwn_init(void)
dwn->screen_height = DisplayHeight(dwn->display, dwn->screen); dwn->screen_height = DisplayHeight(dwn->display, dwn->screen);
dwn->colormap = DefaultColormap(dwn->display, dwn->screen); dwn->colormap = DefaultColormap(dwn->display, dwn->screen);
/* Set error handlers */
XSetErrorHandler(x_error_handler); XSetErrorHandler(x_error_handler);
XSetIOErrorHandler(x_io_error_handler); XSetIOErrorHandler(x_io_error_handler);
/* Check for other WM */
if (check_other_wm()) { if (check_other_wm()) {
fprintf(stderr, "Another window manager is already running\n"); fprintf(stderr, "Another window manager is already running\n");
XCloseDisplay(dwn->display); XCloseDisplay(dwn->display);
return -1; return -1;
} }
/* Initialize logging */
log_init("~/.local/share/dwn/dwn.log"); log_init("~/.local/share/dwn/dwn.log");
LOG_INFO("DWN %s starting", DWN_VERSION); LOG_INFO("DWN %s starting", DWN_VERSION);
/* Load configuration */
dwn->config = config_create(); dwn->config = config_create();
config_load(dwn->config, NULL); config_load(dwn->config, NULL);
/* Initialize colors (after display is open) */
extern void config_init_colors(Config *cfg); extern void config_init_colors(Config *cfg);
config_init_colors(dwn->config); config_init_colors(dwn->config);
/* Load font */
dwn->font = XLoadQueryFont(dwn->display, dwn->config->font_name); dwn->font = XLoadQueryFont(dwn->display, dwn->config->font_name);
if (dwn->font == NULL) { if (dwn->font == NULL) {
dwn->font = XLoadQueryFont(dwn->display, "fixed"); dwn->font = XLoadQueryFont(dwn->display, "fixed");
} }
/* Load Xft font for UTF-8 support */
dwn->xft_font = XftFontOpenName(dwn->display, dwn->screen, dwn->xft_font = XftFontOpenName(dwn->display, dwn->screen,
"monospace:size=10:antialias=true"); "monospace:size=10:antialias=true");
if (dwn->xft_font == NULL) { if (dwn->xft_font == NULL) {
@ -766,7 +689,6 @@ int dwn_init(void)
LOG_INFO("Loaded Xft font for UTF-8 support"); LOG_INFO("Loaded Xft font for UTF-8 support");
} }
/* Create GC */
XGCValues gcv; XGCValues gcv;
gcv.foreground = dwn->config->colors.panel_fg; gcv.foreground = dwn->config->colors.panel_fg;
gcv.background = dwn->config->colors.panel_bg; gcv.background = dwn->config->colors.panel_bg;
@ -774,59 +696,42 @@ int dwn_init(void)
dwn->gc = XCreateGC(dwn->display, dwn->root, dwn->gc = XCreateGC(dwn->display, dwn->root,
GCForeground | GCBackground | (dwn->font ? GCFont : 0), &gcv); GCForeground | GCBackground | (dwn->font ? GCFont : 0), &gcv);
/* Initialize atoms */
atoms_init(dwn->display); atoms_init(dwn->display);
/* Initialize monitors */
init_monitors(); init_monitors();
/* Initialize workspaces */
workspace_init(); workspace_init();
/* Initialize decorations */
decorations_init(); decorations_init();
/* Initialize panels */
panels_init(); panels_init();
/* Initialize system tray (WiFi, Audio indicators) */
systray_init(); systray_init();
/* Initialize news ticker */
news_init(); news_init();
/* Initialize app launcher */
applauncher_init(); applauncher_init();
/* Initialize keyboard shortcuts */
keys_init(); keys_init();
/* Initialize D-Bus notifications */
notifications_init(); notifications_init();
/* Initialize AI */
ai_init(); ai_init();
/* Setup EWMH */
atoms_setup_ewmh(); atoms_setup_ewmh();
/* Select events on root window */
XSelectInput(dwn->display, dwn->root, XSelectInput(dwn->display, dwn->root,
SubstructureRedirectMask | SubstructureNotifyMask | SubstructureRedirectMask | SubstructureNotifyMask |
StructureNotifyMask | PropertyChangeMask | StructureNotifyMask | PropertyChangeMask |
ButtonPressMask | PointerMotionMask); ButtonPressMask | PointerMotionMask);
/* Set cursor */
Cursor cursor = XCreateFontCursor(dwn->display, XC_left_ptr); Cursor cursor = XCreateFontCursor(dwn->display, XC_left_ptr);
XDefineCursor(dwn->display, dwn->root, cursor); XDefineCursor(dwn->display, dwn->root, cursor);
/* Scan existing windows */
scan_existing_windows(); scan_existing_windows();
/* Arrange initial workspace */
workspace_arrange_current(); workspace_arrange_current();
/* Render panels */
panel_render_all(); panel_render_all();
dwn->running = true; dwn->running = true;
@ -840,7 +745,6 @@ void dwn_cleanup(void)
{ {
LOG_INFO("DWN shutting down"); LOG_INFO("DWN shutting down");
/* Cleanup subsystems */
ai_cleanup(); ai_cleanup();
notifications_cleanup(); notifications_cleanup();
news_cleanup(); news_cleanup();
@ -851,12 +755,10 @@ void dwn_cleanup(void)
decorations_cleanup(); decorations_cleanup();
workspace_cleanup(); workspace_cleanup();
/* Unmanage all clients */
while (dwn->client_list != NULL) { while (dwn->client_list != NULL) {
client_unmanage(dwn->client_list); client_unmanage(dwn->client_list);
} }
/* Free resources */
if (dwn->gc != None) { if (dwn->gc != None) {
XFreeGC(dwn->display, dwn->gc); XFreeGC(dwn->display, dwn->gc);
} }
@ -870,7 +772,6 @@ void dwn_cleanup(void)
config_destroy(dwn->config); config_destroy(dwn->config);
} }
/* Close display */
if (dwn->display != NULL) { if (dwn->display != NULL) {
XCloseDisplay(dwn->display); XCloseDisplay(dwn->display);
} }
@ -893,35 +794,28 @@ void dwn_run(void)
long last_news_update = 0; long last_news_update = 0;
while (dwn->running && received_signal == 0) { while (dwn->running && received_signal == 0) {
/* Handle pending X events */
while (XPending(dwn->display)) { while (XPending(dwn->display)) {
XEvent ev; XEvent ev;
XNextEvent(dwn->display, &ev); XNextEvent(dwn->display, &ev);
dwn_handle_event(&ev); dwn_handle_event(&ev);
} }
/* Process D-Bus messages */
notifications_process_messages(); notifications_process_messages();
/* Process AI requests */
ai_process_pending(); ai_process_pending();
/* Process Exa requests */
exa_process_pending(); exa_process_pending();
/* Update notifications (check for expired) */
notifications_update(); notifications_update();
long now = get_time_ms(); long now = get_time_ms();
/* Update news ticker frequently for smooth scrolling (~60fps) */
if (now - last_news_update >= 16) { if (now - last_news_update >= 16) {
news_update(); news_update();
panel_render_all(); panel_render_all();
last_news_update = now; last_news_update = now;
} }
/* Update clock and system stats every second */
if (now - last_clock_update >= 1000) { if (now - last_clock_update >= 1000) {
panel_update_clock(); panel_update_clock();
panel_update_system_stats(); panel_update_system_stats();
@ -929,7 +823,6 @@ void dwn_run(void)
last_clock_update = now; last_clock_update = now;
} }
/* Wait for events with timeout - short for smooth animation */
fd_set fds; fd_set fds;
FD_ZERO(&fds); FD_ZERO(&fds);
FD_SET(x11_fd, &fds); FD_SET(x11_fd, &fds);
@ -939,7 +832,7 @@ void dwn_run(void)
struct timeval tv; struct timeval tv;
tv.tv_sec = 0; tv.tv_sec = 0;
tv.tv_usec = 16000; /* ~16ms for 60fps smooth scrolling */ tv.tv_usec = 16000;
int max_fd = x11_fd; int max_fd = x11_fd;
if (dbus_fd > max_fd) max_fd = dbus_fd; if (dbus_fd > max_fd) max_fd = dbus_fd;
@ -947,7 +840,6 @@ void dwn_run(void)
select(max_fd + 1, &fds, NULL, NULL, &tv); select(max_fd + 1, &fds, NULL, NULL, &tv);
} }
/* Handle signal */
if (received_signal != 0) { if (received_signal != 0) {
LOG_INFO("Received signal %d", received_signal); LOG_INFO("Received signal %d", received_signal);
} }
@ -958,7 +850,6 @@ void dwn_quit(void)
dwn->running = false; dwn->running = false;
} }
/* ========== Main ========== */
static void print_usage(const char *program) static void print_usage(const char *program)
{ {
@ -982,7 +873,6 @@ static void print_version(void)
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
/* Parse arguments */
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
print_usage(argv[0]); print_usage(argv[0]);
@ -994,27 +884,22 @@ int main(int argc, char *argv[])
} }
} }
/* Setup signal handlers */
signal(SIGTERM, signal_handler); signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler); signal(SIGINT, signal_handler);
signal(SIGHUP, signal_handler); signal(SIGHUP, signal_handler);
/* Setup crash signal handlers to flush logs before dying */
signal(SIGSEGV, crash_signal_handler); signal(SIGSEGV, crash_signal_handler);
signal(SIGABRT, crash_signal_handler); signal(SIGABRT, crash_signal_handler);
signal(SIGFPE, crash_signal_handler); signal(SIGFPE, crash_signal_handler);
signal(SIGBUS, crash_signal_handler); signal(SIGBUS, crash_signal_handler);
signal(SIGILL, crash_signal_handler); signal(SIGILL, crash_signal_handler);
/* Initialize */
if (dwn_init() != 0) { if (dwn_init() != 0) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
/* Run event loop */
dwn_run(); dwn_run();
/* Cleanup */
dwn_cleanup(); dwn_cleanup();
return EXIT_SUCCESS; return EXIT_SUCCESS;

View File

@ -20,13 +20,10 @@
#include <curl/curl.h> #include <curl/curl.h>
#include <X11/Xft/Xft.h> #include <X11/Xft/Xft.h>
/* Scroll speed in pixels per second */
#define SCROLL_SPEED_PPS 80 #define SCROLL_SPEED_PPS 80
/* Fetch interval in milliseconds (5 minutes) */
#define FETCH_INTERVAL 300000 #define FETCH_INTERVAL 300000
/* Sentiment colors (RGB hex values) */
#define SENTIMENT_COLOR_POSITIVE 0x81C784 #define SENTIMENT_COLOR_POSITIVE 0x81C784
#define SENTIMENT_COLOR_NEUTRAL 0xB0BEC5 #define SENTIMENT_COLOR_NEUTRAL 0xB0BEC5
#define SENTIMENT_COLOR_NEGATIVE 0xE57373 #define SENTIMENT_COLOR_NEGATIVE 0xE57373
@ -40,15 +37,12 @@ static unsigned long news_sentiment_color(NewsSentiment sentiment)
} }
} }
/* Global state */
NewsState news_state = {0}; NewsState news_state = {0};
/* Thread safety */
static pthread_mutex_t news_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t news_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_t fetch_thread; static pthread_t fetch_thread;
static volatile int fetch_running = 0; static volatile int fetch_running = 0;
/* CURL response buffer */
typedef struct { typedef struct {
char *data; char *data;
size_t size; size_t size;
@ -72,7 +66,6 @@ static size_t news_curl_write_cb(void *contents, size_t size, size_t nmemb, void
return realsize; return realsize;
} }
/* Decode numeric HTML entity (&#123; or &#x1F;) */
static int decode_numeric_entity(const char *src, char *out, size_t max_out) static int decode_numeric_entity(const char *src, char *out, size_t max_out)
{ {
if (src[0] != '&' || src[1] != '#') return 0; if (src[0] != '&' || src[1] != '#') return 0;
@ -106,7 +99,6 @@ static int decode_numeric_entity(const char *src, char *out, size_t max_out)
return 0; return 0;
} }
/* Strip HTML tags and decode entities */
static void strip_html(char *dst, const char *src, size_t max_len) static void strip_html(char *dst, const char *src, size_t max_len)
{ {
size_t j = 0; size_t j = 0;
@ -119,7 +111,6 @@ static void strip_html(char *dst, const char *src, size_t max_len)
in_tag = false; in_tag = false;
} else if (!in_tag) { } else if (!in_tag) {
if (src[i] == '&') { if (src[i] == '&') {
/* Named entities */
if (strncmp(&src[i], "&amp;", 5) == 0) { if (strncmp(&src[i], "&amp;", 5) == 0) {
dst[j++] = '&'; i += 4; dst[j++] = '&'; i += 4;
} else if (strncmp(&src[i], "&lt;", 4) == 0) { } else if (strncmp(&src[i], "&lt;", 4) == 0) {
@ -143,7 +134,6 @@ static void strip_html(char *dst, const char *src, size_t max_len)
} else if (strncmp(&src[i], "&ldquo;", 7) == 0 || strncmp(&src[i], "&rdquo;", 7) == 0) { } else if (strncmp(&src[i], "&ldquo;", 7) == 0 || strncmp(&src[i], "&rdquo;", 7) == 0) {
dst[j++] = '"'; i += 6; dst[j++] = '"'; i += 6;
} else if (src[i+1] == '#') { } else if (src[i+1] == '#') {
/* Numeric entity */
char decoded[4] = {0}; char decoded[4] = {0};
int consumed = decode_numeric_entity(&src[i], decoded, sizeof(decoded)); int consumed = decode_numeric_entity(&src[i], decoded, sizeof(decoded));
if (consumed > 0) { if (consumed > 0) {
@ -166,7 +156,6 @@ static void strip_html(char *dst, const char *src, size_t max_len)
} }
dst[j] = '\0'; dst[j] = '\0';
/* Collapse multiple spaces */
char *read = dst, *write = dst; char *read = dst, *write = dst;
bool last_space = false; bool last_space = false;
while (*read) { while (*read) {
@ -184,7 +173,6 @@ static void strip_html(char *dst, const char *src, size_t max_len)
*write = '\0'; *write = '\0';
} }
/* Parse JSON response and populate articles */
static int parse_news_json(const char *json_str) static int parse_news_json(const char *json_str)
{ {
cJSON *root = cJSON_Parse(json_str); cJSON *root = cJSON_Parse(json_str);
@ -218,7 +206,6 @@ static int parse_news_json(const char *json_str)
strip_html(art->title, title->valuestring, sizeof(art->title)); strip_html(art->title, title->valuestring, sizeof(art->title));
} }
/* Prefer content, fallback to description */
const char *text = NULL; const char *text = NULL;
if (cJSON_IsString(content) && content->valuestring && strlen(content->valuestring) > 0) { if (cJSON_IsString(content) && content->valuestring && strlen(content->valuestring) > 0) {
text = content->valuestring; text = content->valuestring;
@ -229,7 +216,6 @@ static int parse_news_json(const char *json_str)
if (text) { if (text) {
strip_html(art->content, text, sizeof(art->content)); strip_html(art->content, text, sizeof(art->content));
} else { } else {
/* Use title as fallback */
strncpy(art->content, art->title, sizeof(art->content) - 1); strncpy(art->content, art->title, sizeof(art->content) - 1);
} }
@ -269,14 +255,11 @@ static int parse_news_json(const char *json_str)
return count; return count;
} }
/* Separator between articles */
#define NEWS_SEPARATOR " • " #define NEWS_SEPARATOR " • "
#define NEWS_SEPARATOR_LEN 5 #define NEWS_SEPARATOR_LEN 5
/* Forward declarations */
static int news_find_article_at_x(int click_x); static int news_find_article_at_x(int click_x);
/* Get UTF-8 text width using Xft */
static int news_text_width(const char *text) static int news_text_width(const char *text)
{ {
if (dwn == NULL || text == NULL) return 0; if (dwn == NULL || text == NULL) return 0;
@ -295,7 +278,6 @@ static int news_text_width(const char *text)
return strlen(text) * 8; return strlen(text) * 8;
} }
/* Draw UTF-8 text using Xft with clipping */
static void news_draw_text_clipped(Drawable d, int x, int y, const char *text, static void news_draw_text_clipped(Drawable d, int x, int y, const char *text,
unsigned long color, XRectangle *clip) unsigned long color, XRectangle *clip)
{ {
@ -346,7 +328,6 @@ static void news_draw_text_clipped(Drawable d, int x, int y, const char *text,
} }
} }
/* Get display text for an article */
static void get_article_display_text(int index, char *buf, size_t buf_size) static void get_article_display_text(int index, char *buf, size_t buf_size)
{ {
if (index < 0 || index >= news_state.article_count) { if (index < 0 || index >= news_state.article_count) {
@ -361,7 +342,6 @@ static void get_article_display_text(int index, char *buf, size_t buf_size)
} }
} }
/* Recalculate cached widths after fetching */
static void news_recalc_widths(void) static void news_recalc_widths(void)
{ {
if (dwn == NULL) { if (dwn == NULL) {
@ -379,7 +359,6 @@ static void news_recalc_widths(void)
} }
} }
/* Background fetch thread */
static void *news_fetch_thread(void *arg) static void *news_fetch_thread(void *arg)
{ {
(void)arg; (void)arg;
@ -436,14 +415,12 @@ static void *news_fetch_thread(void *arg)
return NULL; return NULL;
} }
/* ========== Public API ========== */
void news_init(void) void news_init(void)
{ {
memset(&news_state, 0, sizeof(news_state)); memset(&news_state, 0, sizeof(news_state));
news_state.last_fetch = 0; news_state.last_fetch = 0;
/* Start initial fetch */
news_fetch_async(); news_fetch_async();
LOG_INFO("News ticker initialized"); LOG_INFO("News ticker initialized");
@ -451,7 +428,6 @@ void news_init(void)
void news_cleanup(void) void news_cleanup(void)
{ {
/* Wait for any pending fetch */
if (fetch_running) { if (fetch_running) {
pthread_join(fetch_thread, NULL); pthread_join(fetch_thread, NULL);
fetch_running = 0; fetch_running = 0;
@ -468,7 +444,6 @@ void news_fetch_async(void)
news_state.fetching = true; news_state.fetching = true;
pthread_mutex_unlock(&news_mutex); pthread_mutex_unlock(&news_mutex);
/* Wait for previous thread if still running */
if (fetch_running) { if (fetch_running) {
pthread_join(fetch_thread, NULL); pthread_join(fetch_thread, NULL);
} }
@ -489,18 +464,15 @@ void news_update(void)
pthread_mutex_lock(&news_mutex); pthread_mutex_lock(&news_mutex);
/* Auto-refresh if interval passed */
if (!news_state.fetching && (now - news_state.last_fetch) >= FETCH_INTERVAL) { if (!news_state.fetching && (now - news_state.last_fetch) >= FETCH_INTERVAL) {
pthread_mutex_unlock(&news_mutex); pthread_mutex_unlock(&news_mutex);
news_fetch_async(); news_fetch_async();
pthread_mutex_lock(&news_mutex); pthread_mutex_lock(&news_mutex);
} }
/* Time-based smooth scrolling */
if (!news_state.interactive_mode && news_state.article_count > 0) { if (!news_state.interactive_mode && news_state.article_count > 0) {
if (news_state.last_scroll_update > 0) { if (news_state.last_scroll_update > 0) {
long delta_ms = now - news_state.last_scroll_update; long delta_ms = now - news_state.last_scroll_update;
/* Calculate smooth sub-pixel scroll amount based on elapsed time */
double scroll_amount = (SCROLL_SPEED_PPS * delta_ms) / 1000.0; double scroll_amount = (SCROLL_SPEED_PPS * delta_ms) / 1000.0;
news_state.scroll_offset += scroll_amount; news_state.scroll_offset += scroll_amount;
} }
@ -567,7 +539,6 @@ void news_open_current(void)
} }
} }
/* Find article at given x position within news area */
static int news_find_article_at_x(int click_x) static int news_find_article_at_x(int click_x)
{ {
if (news_state.article_count == 0 || news_state.total_width == 0) { if (news_state.article_count == 0 || news_state.total_width == 0) {

View File

@ -12,41 +12,33 @@
#include <stdlib.h> #include <stdlib.h>
#include <X11/Xft/Xft.h> #include <X11/Xft/Xft.h>
/* D-Bus connection */
DBusConnection *dbus_conn = NULL; DBusConnection *dbus_conn = NULL;
/* Notification list */
static Notification *notification_list = NULL; static Notification *notification_list = NULL;
static uint32_t next_notification_id = 1; static uint32_t next_notification_id = 1;
/* Notification dimensions - dynamic based on screen size */
#define NOTIFICATION_MIN_WIDTH 280 #define NOTIFICATION_MIN_WIDTH 280
#define NOTIFICATION_PADDING 12 #define NOTIFICATION_PADDING 12
#define NOTIFICATION_MARGIN 10 #define NOTIFICATION_MARGIN 10
/* Get max width: 33% of screen width */
static int notification_max_width(void) static int notification_max_width(void)
{ {
if (dwn == NULL) return 500; if (dwn == NULL) return 500;
return dwn->screen_width / 3; return dwn->screen_width / 3;
} }
/* Get max height: 50% of screen height */
static int notification_max_height(void) static int notification_max_height(void)
{ {
if (dwn == NULL) return 600; if (dwn == NULL) return 600;
return dwn->screen_height / 2; return dwn->screen_height / 2;
} }
/* Default timeout */
#define DEFAULT_TIMEOUT 5000 #define DEFAULT_TIMEOUT 5000
/* ========== UTF-8 text helpers ========== */
/* Get text width using Xft (UTF-8 aware) */
static int notif_text_width(const char *text, int len) static int notif_text_width(const char *text, int len)
{ {
if (text == NULL || dwn == NULL) return len * 7; /* Fallback estimate */ if (text == NULL || dwn == NULL) return len * 7;
if (dwn->xft_font != NULL) { if (dwn->xft_font != NULL) {
XGlyphInfo extents; XGlyphInfo extents;
@ -59,10 +51,9 @@ static int notif_text_width(const char *text, int len)
return XTextWidth(dwn->font, text, len); return XTextWidth(dwn->font, text, len);
} }
return len * 7; /* Fallback estimate */ return len * 7;
} }
/* Get line height */
static int notif_line_height(void) static int notif_line_height(void)
{ {
if (dwn == NULL) return 14; if (dwn == NULL) return 14;
@ -78,7 +69,6 @@ static int notif_line_height(void)
return 14; return 14;
} }
/* Get font ascent */
static int notif_font_ascent(void) static int notif_font_ascent(void)
{ {
if (dwn == NULL) return 12; if (dwn == NULL) return 12;
@ -94,13 +84,11 @@ static int notif_font_ascent(void)
return 12; return 12;
} }
/* Draw text using Xft (UTF-8 aware) */
static void notif_draw_text(Window win, int x, int y, const char *text, static void notif_draw_text(Window win, int x, int y, const char *text,
int len, unsigned long color) int len, unsigned long color)
{ {
if (text == NULL || dwn == NULL || dwn->display == NULL) return; if (text == NULL || dwn == NULL || dwn->display == NULL) return;
/* Use Xft for UTF-8 support */
if (dwn->xft_font != NULL) { if (dwn->xft_font != NULL) {
XftDraw *xft_draw = XftDrawCreate(dwn->display, win, XftDraw *xft_draw = XftDrawCreate(dwn->display, win,
DefaultVisual(dwn->display, dwn->screen), DefaultVisual(dwn->display, dwn->screen),
@ -126,19 +114,16 @@ static void notif_draw_text(Window win, int x, int y, const char *text,
} }
} }
/* Fallback to legacy X11 text */
XSetForeground(dwn->display, dwn->gc, color); XSetForeground(dwn->display, dwn->gc, color);
XDrawString(dwn->display, win, dwn->gc, x, y, text, len); XDrawString(dwn->display, win, dwn->gc, x, y, text, len);
} }
/* ========== Initialization ========== */
bool notifications_init(void) bool notifications_init(void)
{ {
DBusError err; DBusError err;
dbus_error_init(&err); dbus_error_init(&err);
/* Connect to session bus */
dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &err); dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (dbus_error_is_set(&err)) { if (dbus_error_is_set(&err)) {
LOG_ERROR("D-Bus connection error: %s", err.message); LOG_ERROR("D-Bus connection error: %s", err.message);
@ -151,10 +136,8 @@ bool notifications_init(void)
return false; return false;
} }
/* Register notification service */
if (!notifications_register_service()) { if (!notifications_register_service()) {
LOG_WARN("Could not register notification service (another daemon running?)"); LOG_WARN("Could not register notification service (another daemon running?)");
/* Don't fail - we can still function as a WM */
} }
LOG_INFO("Notification daemon initialized"); LOG_INFO("Notification daemon initialized");
@ -163,14 +146,11 @@ bool notifications_init(void)
void notifications_cleanup(void) void notifications_cleanup(void)
{ {
/* Close all notifications */
notification_close_all(); notification_close_all();
/* D-Bus connection is managed by libdbus */
dbus_conn = NULL; dbus_conn = NULL;
} }
/* ========== D-Bus handling ========== */
bool notifications_register_service(void) bool notifications_register_service(void)
{ {
@ -181,7 +161,6 @@ bool notifications_register_service(void)
DBusError err; DBusError err;
dbus_error_init(&err); dbus_error_init(&err);
/* Request the notification service name */
int result = dbus_bus_request_name(dbus_conn, "org.freedesktop.Notifications", int result = dbus_bus_request_name(dbus_conn, "org.freedesktop.Notifications",
DBUS_NAME_FLAG_REPLACE_EXISTING, &err); DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
@ -196,7 +175,6 @@ bool notifications_register_service(void)
return false; return false;
} }
/* Add message filter */
dbus_connection_add_filter(dbus_conn, notifications_handle_message, NULL, NULL); dbus_connection_add_filter(dbus_conn, notifications_handle_message, NULL, NULL);
LOG_INFO("Registered as org.freedesktop.Notifications"); LOG_INFO("Registered as org.freedesktop.Notifications");
@ -209,11 +187,9 @@ void notifications_process_messages(void)
return; return;
} }
/* Process pending D-Bus messages (non-blocking) */
dbus_connection_read_write(dbus_conn, 0); dbus_connection_read_write(dbus_conn, 0);
while (dbus_connection_dispatch(dbus_conn) == DBUS_DISPATCH_DATA_REMAINS) { while (dbus_connection_dispatch(dbus_conn) == DBUS_DISPATCH_DATA_REMAINS) {
/* Keep dispatching */
} }
} }
@ -234,7 +210,6 @@ DBusHandlerResult notifications_handle_message(DBusConnection *conn,
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
} }
/* Handle Notify method */
if (strcmp(member, "Notify") == 0) { if (strcmp(member, "Notify") == 0) {
DBusMessageIter args; DBusMessageIter args;
if (!dbus_message_iter_init(msg, &args)) { if (!dbus_message_iter_init(msg, &args)) {
@ -248,7 +223,6 @@ DBusHandlerResult notifications_handle_message(DBusConnection *conn,
char *body = ""; char *body = "";
int32_t timeout = -1; int32_t timeout = -1;
/* Parse arguments */
if (dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_STRING) { if (dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_STRING) {
dbus_message_iter_get_basic(&args, &app_name); dbus_message_iter_get_basic(&args, &app_name);
dbus_message_iter_next(&args); dbus_message_iter_next(&args);
@ -270,12 +244,10 @@ DBusHandlerResult notifications_handle_message(DBusConnection *conn,
dbus_message_iter_next(&args); dbus_message_iter_next(&args);
} }
/* Skip actions array */
if (dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_ARRAY) { if (dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_ARRAY) {
dbus_message_iter_next(&args); dbus_message_iter_next(&args);
} }
/* Skip hints dict */
if (dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_ARRAY) { if (dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_ARRAY) {
dbus_message_iter_next(&args); dbus_message_iter_next(&args);
} }
@ -284,10 +256,8 @@ DBusHandlerResult notifications_handle_message(DBusConnection *conn,
dbus_message_iter_get_basic(&args, &timeout); dbus_message_iter_get_basic(&args, &timeout);
} }
/* Show notification */
uint32_t id = notification_show(app_name, summary, body, icon, timeout); uint32_t id = notification_show(app_name, summary, body, icon, timeout);
/* Send reply */
DBusMessage *reply = dbus_message_new_method_return(msg); DBusMessage *reply = dbus_message_new_method_return(msg);
dbus_message_append_args(reply, DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID); dbus_message_append_args(reply, DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID);
dbus_connection_send(conn, reply, NULL); dbus_connection_send(conn, reply, NULL);
@ -296,7 +266,6 @@ DBusHandlerResult notifications_handle_message(DBusConnection *conn,
return DBUS_HANDLER_RESULT_HANDLED; return DBUS_HANDLER_RESULT_HANDLED;
} }
/* Handle CloseNotification method */
if (strcmp(member, "CloseNotification") == 0) { if (strcmp(member, "CloseNotification") == 0) {
DBusMessageIter args; DBusMessageIter args;
if (dbus_message_iter_init(msg, &args) && if (dbus_message_iter_init(msg, &args) &&
@ -313,7 +282,6 @@ DBusHandlerResult notifications_handle_message(DBusConnection *conn,
return DBUS_HANDLER_RESULT_HANDLED; return DBUS_HANDLER_RESULT_HANDLED;
} }
/* Handle GetCapabilities method */
if (strcmp(member, "GetCapabilities") == 0) { if (strcmp(member, "GetCapabilities") == 0) {
DBusMessage *reply = dbus_message_new_method_return(msg); DBusMessage *reply = dbus_message_new_method_return(msg);
DBusMessageIter args_iter, array_iter; DBusMessageIter args_iter, array_iter;
@ -333,7 +301,6 @@ DBusHandlerResult notifications_handle_message(DBusConnection *conn,
return DBUS_HANDLER_RESULT_HANDLED; return DBUS_HANDLER_RESULT_HANDLED;
} }
/* Handle GetServerInformation method */
if (strcmp(member, "GetServerInformation") == 0) { if (strcmp(member, "GetServerInformation") == 0) {
DBusMessage *reply = dbus_message_new_method_return(msg); DBusMessage *reply = dbus_message_new_method_return(msg);
@ -358,11 +325,9 @@ DBusHandlerResult notifications_handle_message(DBusConnection *conn,
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
} }
/* ========== Notification management ========== */
#define MAX_VISIBLE_NOTIFICATIONS 3 #define MAX_VISIBLE_NOTIFICATIONS 3
/* Calculate notification size based on content - accounts for word wrapping */
static void notification_calculate_size(const char *summary, const char *body, static void notification_calculate_size(const char *summary, const char *body,
int *out_width, int *out_height) int *out_width, int *out_height)
{ {
@ -375,7 +340,6 @@ static void notification_calculate_size(const char *summary, const char *body,
int height = NOTIFICATION_PADDING * 2; int height = NOTIFICATION_PADDING * 2;
int line_height = notif_line_height(); int line_height = notif_line_height();
/* Add space for summary */
if (summary != NULL && summary[0] != '\0') { if (summary != NULL && summary[0] != '\0') {
int summary_width = NOTIFICATION_PADDING * 2; int summary_width = NOTIFICATION_PADDING * 2;
summary_width += notif_text_width(summary, strlen(summary)); summary_width += notif_text_width(summary, strlen(summary));
@ -383,41 +347,34 @@ static void notification_calculate_size(const char *summary, const char *body,
height += line_height + 4; height += line_height + 4;
} }
/* Count wrapped lines in body */
if (body != NULL && body[0] != '\0') { if (body != NULL && body[0] != '\0') {
const char *p = body; const char *p = body;
int wrapped_line_count = 0; int wrapped_line_count = 0;
while (*p != '\0') { while (*p != '\0') {
/* Find end of this logical line (newline) */
const char *line_start = p; const char *line_start = p;
while (*p != '\0' && *p != '\n') p++; while (*p != '\0' && *p != '\n') p++;
size_t logical_line_len = p - line_start; size_t logical_line_len = p - line_start;
if (logical_line_len == 0) { if (logical_line_len == 0) {
/* Empty line */
wrapped_line_count++; wrapped_line_count++;
} else { } else {
/* Count how many wrapped lines this logical line needs */
const char *lp = line_start; const char *lp = line_start;
while (lp < line_start + logical_line_len) { while (lp < line_start + logical_line_len) {
size_t remaining = (line_start + logical_line_len) - lp; size_t remaining = (line_start + logical_line_len) - lp;
size_t fit_len = 0; size_t fit_len = 0;
/* Find how much fits on one line */
for (size_t i = 0; i < remaining; i++) { for (size_t i = 0; i < remaining; i++) {
if ((lp[i] & 0xC0) == 0x80) continue; /* Skip continuation bytes */ if ((lp[i] & 0xC0) == 0x80) continue;
int w = notif_text_width(lp, i + 1); int w = notif_text_width(lp, i + 1);
if (w > content_max_width) break; if (w > content_max_width) break;
fit_len = i + 1; fit_len = i + 1;
} }
/* Include trailing UTF-8 continuation bytes */
while (fit_len < remaining && (lp[fit_len] & 0xC0) == 0x80) { while (fit_len < remaining && (lp[fit_len] & 0xC0) == 0x80) {
fit_len++; fit_len++;
} }
/* Force at least one character */
if (fit_len == 0 && remaining > 0) { if (fit_len == 0 && remaining > 0) {
fit_len = 1; fit_len = 1;
while (fit_len < remaining && (lp[fit_len] & 0xC0) == 0x80) { while (fit_len < remaining && (lp[fit_len] & 0xC0) == 0x80) {
@ -428,12 +385,10 @@ static void notification_calculate_size(const char *summary, const char *body,
if (fit_len == 0) break; if (fit_len == 0) break;
lp += fit_len; lp += fit_len;
/* Skip leading spaces on continuation */
while (lp < line_start + logical_line_len && *lp == ' ') lp++; while (lp < line_start + logical_line_len && *lp == ' ') lp++;
wrapped_line_count++; wrapped_line_count++;
/* Stop if we'd exceed max height */
if (height + (wrapped_line_count * line_height) > max_height - line_height - NOTIFICATION_PADDING) { if (height + (wrapped_line_count * line_height) > max_height - line_height - NOTIFICATION_PADDING) {
goto done_counting; goto done_counting;
} }
@ -447,13 +402,10 @@ done_counting:
height += wrapped_line_count * line_height; height += wrapped_line_count * line_height;
} }
/* Add space for app name at bottom */
height += line_height + NOTIFICATION_PADDING; height += line_height + NOTIFICATION_PADDING;
/* Use full max_width since we're wrapping, not truncating */
width = max_width; width = max_width;
/* Clamp to min/max */
if (width < NOTIFICATION_MIN_WIDTH) width = NOTIFICATION_MIN_WIDTH; if (width < NOTIFICATION_MIN_WIDTH) width = NOTIFICATION_MIN_WIDTH;
if (width > max_width) width = max_width; if (width > max_width) width = max_width;
if (height < min_height) height = min_height; if (height < min_height) height = min_height;
@ -466,15 +418,13 @@ done_counting:
uint32_t notification_show(const char *app_name, const char *summary, uint32_t notification_show(const char *app_name, const char *summary,
const char *body, const char *icon, int timeout) const char *body, const char *icon, int timeout)
{ {
/* Count existing notifications and close oldest if we have too many */
int count = 0; int count = 0;
Notification *oldest = NULL; Notification *oldest = NULL;
for (Notification *n = notification_list; n != NULL; n = n->next) { for (Notification *n = notification_list; n != NULL; n = n->next) {
count++; count++;
oldest = n; /* Last in list is oldest (we prepend new ones) */ oldest = n;
} }
/* Close oldest notification if we're at the limit */
if (count >= MAX_VISIBLE_NOTIFICATIONS && oldest != NULL) { if (count >= MAX_VISIBLE_NOTIFICATIONS && oldest != NULL) {
notification_close(oldest->id); notification_close(oldest->id);
} }
@ -486,7 +436,6 @@ uint32_t notification_show(const char *app_name, const char *summary,
if (app_name) strncpy(notif->app_name, app_name, sizeof(notif->app_name) - 1); if (app_name) strncpy(notif->app_name, app_name, sizeof(notif->app_name) - 1);
if (summary) strncpy(notif->summary, summary, sizeof(notif->summary) - 1); if (summary) strncpy(notif->summary, summary, sizeof(notif->summary) - 1);
/* Dynamically allocate body - unlimited size */
if (body != NULL && body[0] != '\0') { if (body != NULL && body[0] != '\0') {
notif->body_len = strlen(body); notif->body_len = strlen(body);
notif->body = dwn_malloc(notif->body_len + 1); notif->body = dwn_malloc(notif->body_len + 1);
@ -505,13 +454,11 @@ uint32_t notification_show(const char *app_name, const char *summary,
notif->expire_time = (notif->timeout > 0) ? notif->expire_time = (notif->timeout > 0) ?
get_time_ms() + notif->timeout : 0; get_time_ms() + notif->timeout : 0;
/* Calculate size based on content */
int notif_width, notif_height; int notif_width, notif_height;
notification_calculate_size(summary, body, &notif_width, &notif_height); notification_calculate_size(summary, body, &notif_width, &notif_height);
notif->width = notif_width; notif->width = notif_width;
notif->height = notif_height; notif->height = notif_height;
/* Create notification window */
if (dwn != NULL && dwn->display != NULL) { if (dwn != NULL && dwn->display != NULL) {
XSetWindowAttributes swa; XSetWindowAttributes swa;
swa.override_redirect = True; swa.override_redirect = True;
@ -530,11 +477,9 @@ uint32_t notification_show(const char *app_name, const char *summary,
dwn->config->colors.border_focused); dwn->config->colors.border_focused);
} }
/* Add to list */
notif->next = notification_list; notif->next = notification_list;
notification_list = notif; notification_list = notif;
/* Position and show */
notifications_position(); notifications_position();
notification_render(notif); notification_render(notif);
@ -555,19 +500,16 @@ void notification_close(uint32_t id)
while (notif != NULL) { while (notif != NULL) {
if (notif->id == id) { if (notif->id == id) {
/* Remove from list */
if (prev != NULL) { if (prev != NULL) {
prev->next = notif->next; prev->next = notif->next;
} else { } else {
notification_list = notif->next; notification_list = notif->next;
} }
/* Destroy window */
if (notif->window != None && dwn != NULL && dwn->display != NULL) { if (notif->window != None && dwn != NULL && dwn->display != NULL) {
XDestroyWindow(dwn->display, notif->window); XDestroyWindow(dwn->display, notif->window);
} }
/* Free dynamically allocated body */
if (notif->body != NULL) { if (notif->body != NULL) {
dwn_free(notif->body); dwn_free(notif->body);
notif->body = NULL; notif->body = NULL;
@ -575,7 +517,6 @@ void notification_close(uint32_t id)
dwn_free(notif); dwn_free(notif);
/* Reposition remaining notifications */
notifications_position(); notifications_position();
return; return;
} }
@ -612,7 +553,6 @@ Notification *notification_find_by_window(Window window)
return NULL; return NULL;
} }
/* ========== Rendering ========== */
void notification_render(Notification *notif) void notification_render(Notification *notif)
{ {
@ -642,66 +582,54 @@ void notification_render(Notification *notif)
LOG_DEBUG("Rendering notification: summary='%s', body_len=%zu", LOG_DEBUG("Rendering notification: summary='%s', body_len=%zu",
notif->summary, notif->body_len); notif->summary, notif->body_len);
/* Set legacy font in GC if available (for fallback) */
if (dwn->font != NULL) { if (dwn->font != NULL) {
XSetFont(dpy, dwn->gc, dwn->font->fid); XSetFont(dpy, dwn->gc, dwn->font->fid);
} }
/* Clear background using dynamic size */
XSetForeground(dpy, dwn->gc, colors->notification_bg); XSetForeground(dpy, dwn->gc, colors->notification_bg);
XFillRectangle(dpy, notif->window, dwn->gc, 0, 0, XFillRectangle(dpy, notif->window, dwn->gc, 0, 0,
notif->width, notif->height); notif->width, notif->height);
/* Draw summary (title) */
int line_height = notif_line_height(); int line_height = notif_line_height();
int y = NOTIFICATION_PADDING + notif_font_ascent(); int y = NOTIFICATION_PADDING + notif_font_ascent();
notif_draw_text(notif->window, NOTIFICATION_PADDING, y, notif_draw_text(notif->window, NOTIFICATION_PADDING, y,
notif->summary, strlen(notif->summary), notif->summary, strlen(notif->summary),
colors->notification_fg); colors->notification_fg);
/* Draw body - handle multiple lines */
y += line_height + 4; y += line_height + 4;
int max_width = notif->width - 2 * NOTIFICATION_PADDING; int max_width = notif->width - 2 * NOTIFICATION_PADDING;
int max_y = notif->height - line_height - NOTIFICATION_PADDING; int max_y = notif->height - line_height - NOTIFICATION_PADDING;
/* Only process body if it exists */
if (notif->body != NULL && notif->body_len > 0) { if (notif->body != NULL && notif->body_len > 0) {
/* Dynamically allocate body copy for tokenization */
char *body_copy = dwn_malloc(notif->body_len + 1); char *body_copy = dwn_malloc(notif->body_len + 1);
if (body_copy != NULL) { if (body_copy != NULL) {
memcpy(body_copy, notif->body, notif->body_len + 1); memcpy(body_copy, notif->body, notif->body_len + 1);
/* Split by newlines and draw each line */
char *line = body_copy; char *line = body_copy;
char *next; char *next;
while (line != NULL && *line != '\0' && y < max_y) { while (line != NULL && *line != '\0' && y < max_y) {
/* Find next newline */
next = strchr(line, '\n'); next = strchr(line, '\n');
if (next != NULL) { if (next != NULL) {
*next = '\0'; *next = '\0';
next++; next++;
} }
/* Skip empty lines but count them for spacing */
if (*line == '\0') { if (*line == '\0') {
y += line_height / 2; /* Half-height for empty lines */ y += line_height / 2;
line = next; line = next;
continue; continue;
} }
/* Word wrap long lines instead of truncating */
size_t line_len = strlen(line); size_t line_len = strlen(line);
const char *p = line; const char *p = line;
while (*p != '\0' && y < max_y) { while (*p != '\0' && y < max_y) {
/* Find how much text fits on this line */
size_t fit_len = 0; size_t fit_len = 0;
size_t last_space = 0; size_t last_space = 0;
for (size_t i = 0; p[i] != '\0'; i++) { for (size_t i = 0; p[i] != '\0'; i++) {
/* Skip UTF-8 continuation bytes for character counting */
if ((p[i] & 0xC0) == 0x80) continue; if ((p[i] & 0xC0) == 0x80) continue;
int width = notif_text_width(p, i + 1); int width = notif_text_width(p, i + 1);
@ -710,18 +638,15 @@ void notification_render(Notification *notif)
} }
fit_len = i + 1; fit_len = i + 1;
/* Track last space for word wrapping */
if (p[i] == ' ') { if (p[i] == ' ') {
last_space = i; last_space = i;
} }
} }
/* Adjust to include full UTF-8 characters */
while (fit_len < line_len && (p[fit_len] & 0xC0) == 0x80) { while (fit_len < line_len && (p[fit_len] & 0xC0) == 0x80) {
fit_len++; fit_len++;
} }
/* If we couldn't fit anything, force at least one character */
if (fit_len == 0 && *p != '\0') { if (fit_len == 0 && *p != '\0') {
fit_len = 1; fit_len = 1;
while (fit_len < line_len && (p[fit_len] & 0xC0) == 0x80) { while (fit_len < line_len && (p[fit_len] & 0xC0) == 0x80) {
@ -729,12 +654,10 @@ void notification_render(Notification *notif)
} }
} }
/* Prefer breaking at word boundary if possible */
if (fit_len < strlen(p) && last_space > 0 && last_space > fit_len / 2) { if (fit_len < strlen(p) && last_space > 0 && last_space > fit_len / 2) {
fit_len = last_space + 1; /* Include the space */ fit_len = last_space + 1;
} }
/* Draw this segment */
if (fit_len > 0) { if (fit_len > 0) {
char *segment = dwn_malloc(fit_len + 1); char *segment = dwn_malloc(fit_len + 1);
if (segment != NULL) { if (segment != NULL) {
@ -748,7 +671,6 @@ void notification_render(Notification *notif)
} }
p += fit_len; p += fit_len;
/* Skip leading spaces on new line */
while (*p == ' ') p++; while (*p == ' ') p++;
y += line_height; y += line_height;
@ -764,7 +686,6 @@ void notification_render(Notification *notif)
} }
} }
/* Draw app name at bottom */
if (notif->app_name[0] != '\0') { if (notif->app_name[0] != '\0') {
y = notif->height - NOTIFICATION_PADDING; y = notif->height - NOTIFICATION_PADDING;
notif_draw_text(notif->window, NOTIFICATION_PADDING, y, notif_draw_text(notif->window, NOTIFICATION_PADDING, y,
@ -772,7 +693,6 @@ void notification_render(Notification *notif)
colors->workspace_inactive); colors->workspace_inactive);
} }
/* Force the drawing to be sent to X server */
XFlush(dpy); XFlush(dpy);
} }
@ -787,7 +707,6 @@ void notifications_update(void)
{ {
long now = get_time_ms(); long now = get_time_ms();
/* Check for expired notifications */
Notification *notif = notification_list; Notification *notif = notification_list;
while (notif != NULL) { while (notif != NULL) {
Notification *next = notif->next; Notification *next = notif->next;
@ -808,18 +727,15 @@ void notifications_position(void)
int y = NOTIFICATION_MARGIN; int y = NOTIFICATION_MARGIN;
/* Account for top panel */
if (dwn->config && dwn->config->top_panel_enabled) { if (dwn->config && dwn->config->top_panel_enabled) {
y += config_get_panel_height(); y += config_get_panel_height();
} }
for (Notification *n = notification_list; n != NULL; n = n->next) { for (Notification *n = notification_list; n != NULL; n = n->next) {
if (n->window != None) { if (n->window != None) {
/* Position from right edge using notification's own width */
int x = dwn->screen_width - n->width - NOTIFICATION_MARGIN; int x = dwn->screen_width - n->width - NOTIFICATION_MARGIN;
XMoveWindow(dwn->display, n->window, x, y); XMoveWindow(dwn->display, n->window, x, y);
} }
/* Stack using notification's own height */
y += n->height + NOTIFICATION_MARGIN; y += n->height + NOTIFICATION_MARGIN;
} }
} }
@ -830,7 +746,6 @@ void notifications_raise_all(void)
return; return;
} }
/* Raise all notification windows to the top */
for (Notification *n = notification_list; n != NULL; n = n->next) { for (Notification *n = notification_list; n != NULL; n = n->next) {
if (n->window != None) { if (n->window != None) {
XRaiseWindow(dwn->display, n->window); XRaiseWindow(dwn->display, n->window);
@ -838,7 +753,6 @@ void notifications_raise_all(void)
} }
} }
/* ========== Server info ========== */
void notifications_get_server_info(const char **name, const char **vendor, void notifications_get_server_info(const char **name, const char **vendor,
const char **version, const char **spec_version) const char **version, const char **spec_version)

View File

@ -21,43 +21,35 @@
#include <stdlib.h> #include <stdlib.h>
#include <X11/Xft/Xft.h> #include <X11/Xft/Xft.h>
/* Panel padding and spacing */
#define PANEL_PADDING 8 #define PANEL_PADDING 8
#define WIDGET_SPACING 12 #define WIDGET_SPACING 12
#define WORKSPACE_WIDTH 28 #define WORKSPACE_WIDTH 28
#define TASKBAR_ITEM_WIDTH 150 #define TASKBAR_ITEM_WIDTH 150
/* Clock format */
#define CLOCK_FORMAT "%H:%M:%S" #define CLOCK_FORMAT "%H:%M:%S"
#define DATE_FORMAT "%Y-%m-%d" #define DATE_FORMAT "%Y-%m-%d"
/* Static clock buffer */
static char clock_buffer[32] = ""; static char clock_buffer[32] = "";
static char date_buffer[32] = ""; static char date_buffer[32] = "";
/* System stats */
typedef struct { typedef struct {
int cpu_percent; /* CPU usage percentage */ int cpu_percent;
int mem_percent; /* Memory usage percentage */ int mem_percent;
int mem_used_mb; /* Memory used in MB */ int mem_used_mb;
int mem_total_mb; /* Total memory in MB */ int mem_total_mb;
float load_1min; /* 1-minute load average */ float load_1min;
float load_5min; /* 5-minute load average */ float load_5min;
float load_15min; /* 15-minute load average */ float load_15min;
/* CPU calculation state */
unsigned long long prev_idle; unsigned long long prev_idle;
unsigned long long prev_total; unsigned long long prev_total;
} SystemStats; } SystemStats;
static SystemStats sys_stats = {0}; static SystemStats sys_stats = {0};
/* Forward declarations */
static void panel_render_system_stats(Panel *panel, int x, int *width); static void panel_render_system_stats(Panel *panel, int x, int *width);
static int panel_calculate_stats_width(void); static int panel_calculate_stats_width(void);
/* ========== UTF-8 text helpers ========== */
/* Get text width using Xft (UTF-8 aware) */
static int panel_text_width(const char *text, int len) static int panel_text_width(const char *text, int len)
{ {
if (text == NULL || dwn == NULL) return 0; if (text == NULL || dwn == NULL) return 0;
@ -69,7 +61,6 @@ static int panel_text_width(const char *text, int len)
return extents.xOff; return extents.xOff;
} }
/* Fallback to legacy font */
if (dwn->font != NULL) { if (dwn->font != NULL) {
return XTextWidth(dwn->font, text, len); return XTextWidth(dwn->font, text, len);
} }
@ -77,13 +68,11 @@ static int panel_text_width(const char *text, int len)
return 0; return 0;
} }
/* Draw text using Xft (UTF-8 aware) */
static void panel_draw_text(Drawable d, int x, int y, const char *text, static void panel_draw_text(Drawable d, int x, int y, const char *text,
int len, unsigned long color) int len, unsigned long color)
{ {
if (text == NULL || dwn == NULL || dwn->display == NULL) return; if (text == NULL || dwn == NULL || dwn->display == NULL) return;
/* Use Xft for UTF-8 support */
if (dwn->xft_font != NULL) { if (dwn->xft_font != NULL) {
XftDraw *xft_draw = XftDrawCreate(dwn->display, d, XftDraw *xft_draw = XftDrawCreate(dwn->display, d,
DefaultVisual(dwn->display, dwn->screen), DefaultVisual(dwn->display, dwn->screen),
@ -109,12 +98,10 @@ static void panel_draw_text(Drawable d, int x, int y, const char *text,
} }
} }
/* Fallback to legacy X11 text */
XSetForeground(dwn->display, dwn->gc, color); XSetForeground(dwn->display, dwn->gc, color);
XDrawString(dwn->display, d, dwn->gc, x, y, text, len); XDrawString(dwn->display, d, dwn->gc, x, y, text, len);
} }
/* Get text Y position for vertical centering */
static int panel_text_y(int panel_height) static int panel_text_y(int panel_height)
{ {
if (dwn->xft_font != NULL) { if (dwn->xft_font != NULL) {
@ -126,7 +113,6 @@ static int panel_text_y(int panel_height)
return panel_height / 2; return panel_height / 2;
} }
/* ========== Panel creation/destruction ========== */
Panel *panel_create(PanelPosition position) Panel *panel_create(PanelPosition position)
{ {
@ -147,7 +133,6 @@ Panel *panel_create(PanelPosition position)
panel->y = dwn->screen_height - panel->height; panel->y = dwn->screen_height - panel->height;
} }
/* Create panel window */
XSetWindowAttributes swa; XSetWindowAttributes swa;
swa.override_redirect = True; swa.override_redirect = True;
swa.background_pixel = dwn->config->colors.panel_bg; swa.background_pixel = dwn->config->colors.panel_bg;
@ -161,12 +146,10 @@ Panel *panel_create(PanelPosition position)
CWOverrideRedirect | CWBackPixel | CWEventMask, CWOverrideRedirect | CWBackPixel | CWEventMask,
&swa); &swa);
/* Create double buffer */
panel->buffer = XCreatePixmap(dwn->display, panel->window, panel->buffer = XCreatePixmap(dwn->display, panel->window,
panel->width, panel->height, panel->width, panel->height,
DefaultDepth(dwn->display, dwn->screen)); DefaultDepth(dwn->display, dwn->screen));
/* Set EWMH strut to reserve space */
long strut[4] = { 0, 0, 0, 0 }; long strut[4] = { 0, 0, 0, 0 };
if (position == PANEL_TOP) { if (position == PANEL_TOP) {
strut[2] = panel->height; strut[2] = panel->height;
@ -221,7 +204,6 @@ void panels_init(void)
} }
} }
/* Initial clock and stats update */
panel_update_clock(); panel_update_clock();
panel_update_system_stats(); panel_update_system_stats();
@ -241,7 +223,6 @@ void panels_cleanup(void)
} }
} }
/* ========== Panel rendering ========== */
void panel_render(Panel *panel) void panel_render(Panel *panel)
{ {
@ -252,7 +233,6 @@ void panel_render(Panel *panel)
Display *dpy = dwn->display; Display *dpy = dwn->display;
const ColorScheme *colors = config_get_colors(); const ColorScheme *colors = config_get_colors();
/* Clear buffer with background color */
XSetForeground(dpy, dwn->gc, colors->panel_bg); XSetForeground(dpy, dwn->gc, colors->panel_bg);
XFillRectangle(dpy, panel->buffer, dwn->gc, 0, 0, panel->width, panel->height); XFillRectangle(dpy, panel->buffer, dwn->gc, 0, 0, panel->width, panel->height);
@ -260,33 +240,25 @@ void panel_render(Panel *panel)
int width; int width;
if (panel->position == PANEL_TOP) { if (panel->position == PANEL_TOP) {
/* Top panel: workspaces, layout indicator, taskbar, systray */
/* Workspace indicators */
panel_render_workspaces(panel, x, &width); panel_render_workspaces(panel, x, &width);
x += width + WIDGET_SPACING; x += width + WIDGET_SPACING;
/* Layout indicator */
panel_render_layout_indicator(panel, x, &width); panel_render_layout_indicator(panel, x, &width);
x += width + WIDGET_SPACING; x += width + WIDGET_SPACING;
/* Taskbar (takes remaining space, but leave room for systray) */
panel_render_taskbar(panel, x, &width); panel_render_taskbar(panel, x, &width);
/* System tray (right side) - WiFi, Audio, etc. */
int systray_actual_width = systray_get_width(); int systray_actual_width = systray_get_width();
int systray_x = panel->width - systray_actual_width - PANEL_PADDING; int systray_x = panel->width - systray_actual_width - PANEL_PADDING;
systray_render(panel, systray_x, &width); systray_render(panel, systray_x, &width);
/* AI status (left of systray) */
if (dwn->ai_enabled) { if (dwn->ai_enabled) {
int ai_x = systray_x - 60; int ai_x = systray_x - 60;
panel_render_ai_status(panel, ai_x, &width); panel_render_ai_status(panel, ai_x, &width);
} }
} else { } else {
/* Bottom panel: date (left), news ticker (center), system stats + clock (right) */
/* Date (left side) */
int date_width = 0; int date_width = 0;
if (dwn->xft_font != NULL || dwn->font != NULL) { if (dwn->xft_font != NULL || dwn->font != NULL) {
int text_y = panel_text_y(panel->height); int text_y = panel_text_y(panel->height);
@ -295,28 +267,23 @@ void panel_render(Panel *panel)
date_width = panel_text_width(date_buffer, strlen(date_buffer)); date_width = panel_text_width(date_buffer, strlen(date_buffer));
} }
/* Calculate positions from right edge */
int clock_width = panel_text_width(clock_buffer, strlen(clock_buffer)); int clock_width = panel_text_width(clock_buffer, strlen(clock_buffer));
int stats_width = panel_calculate_stats_width(); int stats_width = panel_calculate_stats_width();
/* Clock at rightmost position */
int clock_x = panel->width - clock_width - PANEL_PADDING; int clock_x = panel->width - clock_width - PANEL_PADDING;
panel_render_clock(panel, clock_x, &width); panel_render_clock(panel, clock_x, &width);
/* Stats immediately left of clock */
int stats_x = clock_x - stats_width - WIDGET_SPACING; int stats_x = clock_x - stats_width - WIDGET_SPACING;
panel_render_system_stats(panel, stats_x, &width); panel_render_system_stats(panel, stats_x, &width);
/* News ticker between date and stats */
int news_start = PANEL_PADDING + date_width + WIDGET_SPACING * 2; int news_start = PANEL_PADDING + date_width + WIDGET_SPACING * 2;
int news_max_width = stats_x - news_start - WIDGET_SPACING; int news_max_width = stats_x - news_start - WIDGET_SPACING;
if (news_max_width > 100) { /* Only show if there's reasonable space */ if (news_max_width > 100) {
int news_width = 0; int news_width = 0;
news_render(panel, news_start, news_max_width, &news_width); news_render(panel, news_start, news_max_width, &news_width);
} }
} }
/* Copy buffer to window */
XCopyArea(dpy, panel->buffer, panel->window, dwn->gc, XCopyArea(dpy, panel->buffer, panel->window, dwn->gc,
0, 0, panel->width, panel->height, 0, 0); 0, 0, panel->width, panel->height, 0, 0);
@ -349,14 +316,12 @@ void panel_render_workspaces(Panel *panel, int x, int *width)
bool active = (i == dwn->current_workspace); bool active = (i == dwn->current_workspace);
bool has_clients = !workspace_is_empty(i); bool has_clients = !workspace_is_empty(i);
/* Background */
unsigned long bg = active ? colors->workspace_active : colors->panel_bg; unsigned long bg = active ? colors->workspace_active : colors->panel_bg;
XSetForeground(dpy, dwn->gc, bg); XSetForeground(dpy, dwn->gc, bg);
XFillRectangle(dpy, panel->buffer, dwn->gc, XFillRectangle(dpy, panel->buffer, dwn->gc,
x + i * WORKSPACE_WIDTH, 2, x + i * WORKSPACE_WIDTH, 2,
WORKSPACE_WIDTH - 2, panel->height - 4); WORKSPACE_WIDTH - 2, panel->height - 4);
/* Workspace number */
char num[4]; char num[4];
snprintf(num, sizeof(num), "%d", i + 1); snprintf(num, sizeof(num), "%d", i + 1);
@ -384,7 +349,6 @@ void panel_render_taskbar(Panel *panel, int x, int *width)
int available_width = panel->width - x - 100 - PANEL_PADDING; int available_width = panel->width - x - 100 - PANEL_PADDING;
int item_count = 0; int item_count = 0;
/* Count visible clients */
for (Client *c = dwn->client_list; c != NULL; c = c->next) { for (Client *c = dwn->client_list; c != NULL; c = c->next) {
if (c->workspace == (unsigned int)dwn->current_workspace && !client_is_minimized(c)) { if (c->workspace == (unsigned int)dwn->current_workspace && !client_is_minimized(c)) {
item_count++; item_count++;
@ -401,7 +365,6 @@ void panel_render_taskbar(Panel *panel, int x, int *width)
item_width = TASKBAR_ITEM_WIDTH; item_width = TASKBAR_ITEM_WIDTH;
} }
/* Render each client */
Workspace *ws = workspace_get_current(); Workspace *ws = workspace_get_current();
for (Client *c = dwn->client_list; c != NULL; c = c->next) { for (Client *c = dwn->client_list; c != NULL; c = c->next) {
@ -411,32 +374,27 @@ void panel_render_taskbar(Panel *panel, int x, int *width)
bool focused = (ws != NULL && ws->focused == c); bool focused = (ws != NULL && ws->focused == c);
/* Background */
unsigned long bg = focused ? colors->workspace_active : colors->panel_bg; unsigned long bg = focused ? colors->workspace_active : colors->panel_bg;
XSetForeground(dpy, dwn->gc, bg); XSetForeground(dpy, dwn->gc, bg);
XFillRectangle(dpy, panel->buffer, dwn->gc, XFillRectangle(dpy, panel->buffer, dwn->gc,
current_x, 2, item_width - 2, panel->height - 4); current_x, 2, item_width - 2, panel->height - 4);
/* Title - leave room for "..." (4 bytes including null) */
char title[64]; char title[64];
strncpy(title, c->title, sizeof(title) - 4); /* Leave room for "..." */ strncpy(title, c->title, sizeof(title) - 4);
title[sizeof(title) - 4] = '\0'; title[sizeof(title) - 4] = '\0';
/* Truncate UTF-8 aware if necessary */
int max_text_width = item_width - 8; int max_text_width = item_width - 8;
bool truncated = false; bool truncated = false;
while (panel_text_width(title, strlen(title)) > max_text_width && strlen(title) > 3) { while (panel_text_width(title, strlen(title)) > max_text_width && strlen(title) > 3) {
size_t len = strlen(title); size_t len = strlen(title);
/* Move back to find UTF-8 character boundary */
size_t cut = len - 1; size_t cut = len - 1;
while (cut > 0 && (title[cut] & 0xC0) == 0x80) { while (cut > 0 && (title[cut] & 0xC0) == 0x80) {
cut--; /* Skip continuation bytes */ cut--;
} }
if (cut > 0) cut--; if (cut > 0) cut--;
while (cut > 0 && (title[cut] & 0xC0) == 0x80) { while (cut > 0 && (title[cut] & 0xC0) == 0x80) {
cut--; cut--;
} }
/* Ensure we have room for "..." */
if (cut > sizeof(title) - 4) { if (cut > sizeof(title) - 4) {
cut = sizeof(title) - 4; cut = sizeof(title) - 4;
} }
@ -476,8 +434,6 @@ void panel_render_clock(Panel *panel, int x, int *width)
void panel_render_systray(Panel *panel, int x, int *width) void panel_render_systray(Panel *panel, int x, int *width)
{ {
/* System tray placeholder - actual implementation requires
handling _NET_SYSTEM_TRAY protocol */
(void)panel; (void)panel;
(void)x; (void)x;
*width = 0; *width = 0;
@ -524,7 +480,6 @@ void panel_render_ai_status(Panel *panel, int x, int *width)
*width = panel_text_width(status, strlen(status)); *width = panel_text_width(status, strlen(status));
} }
/* ========== Panel interaction ========== */
void panel_handle_click(Panel *panel, int x, int y, int button) void panel_handle_click(Panel *panel, int x, int y, int button)
{ {
@ -533,7 +488,6 @@ void panel_handle_click(Panel *panel, int x, int y, int button)
} }
if (panel->position == PANEL_TOP) { if (panel->position == PANEL_TOP) {
/* Check systray click first (right side) */
int systray_actual_width = systray_get_width(); int systray_actual_width = systray_get_width();
int systray_start = panel->width - systray_actual_width - PANEL_PADDING; int systray_start = panel->width - systray_actual_width - PANEL_PADDING;
if (x >= systray_start) { if (x >= systray_start) {
@ -541,27 +495,24 @@ void panel_handle_click(Panel *panel, int x, int y, int button)
return; return;
} }
/* Check workspace click */
int ws = panel_hit_test_workspace(panel, x, y); int ws = panel_hit_test_workspace(panel, x, y);
if (ws >= 0 && ws < MAX_WORKSPACES) { if (ws >= 0 && ws < MAX_WORKSPACES) {
if (button == 1) { /* Left click */ if (button == 1) {
workspace_switch(ws); workspace_switch(ws);
} }
return; return;
} }
/* Check taskbar click */
Client *c = panel_hit_test_taskbar(panel, x, y); Client *c = panel_hit_test_taskbar(panel, x, y);
if (c != NULL) { if (c != NULL) {
if (button == 1) { if (button == 1) {
client_focus(c); client_focus(c);
} else if (button == 3) { /* Right click */ } else if (button == 3) {
client_close(c); client_close(c);
} }
return; return;
} }
} else if (panel->position == PANEL_BOTTOM) { } else if (panel->position == PANEL_BOTTOM) {
/* Bottom panel - click on news opens article in browser */
if (button == 1) { if (button == 1) {
news_handle_click(x, y); news_handle_click(x, y);
} }
@ -629,7 +580,6 @@ Client *panel_hit_test_taskbar(Panel *panel, int x, int y)
return NULL; return NULL;
} }
/* ========== Panel visibility ========== */
void panel_show(Panel *panel) void panel_show(Panel *panel)
{ {
@ -665,7 +615,6 @@ void panel_toggle(Panel *panel)
} }
} }
/* ========== Clock updates ========== */
void panel_update_clock(void) void panel_update_clock(void)
{ {
@ -676,14 +625,12 @@ void panel_update_clock(void)
strftime(date_buffer, sizeof(date_buffer), DATE_FORMAT, tm_info); strftime(date_buffer, sizeof(date_buffer), DATE_FORMAT, tm_info);
} }
/* ========== System stats ========== */
void panel_update_system_stats(void) void panel_update_system_stats(void)
{ {
FILE *fp; FILE *fp;
char line[256]; char line[256];
/* Read CPU stats from /proc/stat */
fp = fopen("/proc/stat", "r"); fp = fopen("/proc/stat", "r");
if (fp != NULL) { if (fp != NULL) {
if (fgets(line, sizeof(line), fp) != NULL) { if (fgets(line, sizeof(line), fp) != NULL) {
@ -708,7 +655,6 @@ void panel_update_system_stats(void)
fclose(fp); fclose(fp);
} }
/* Read memory stats from /proc/meminfo */
fp = fopen("/proc/meminfo", "r"); fp = fopen("/proc/meminfo", "r");
if (fp != NULL) { if (fp != NULL) {
unsigned long mem_total = 0, mem_free = 0, buffers = 0, cached = 0; unsigned long mem_total = 0, mem_free = 0, buffers = 0, cached = 0;
@ -722,7 +668,7 @@ void panel_update_system_stats(void)
sscanf(line + 8, " %lu", &buffers); sscanf(line + 8, " %lu", &buffers);
} else if (strncmp(line, "Cached:", 7) == 0) { } else if (strncmp(line, "Cached:", 7) == 0) {
sscanf(line + 7, " %lu", &cached); sscanf(line + 7, " %lu", &cached);
break; /* Got all we need */ break;
} }
} }
fclose(fp); fclose(fp);
@ -735,7 +681,6 @@ void panel_update_system_stats(void)
} }
} }
/* Read load average from /proc/loadavg */
fp = fopen("/proc/loadavg", "r"); fp = fopen("/proc/loadavg", "r");
if (fp != NULL) { if (fp != NULL) {
if (fscanf(fp, "%f %f %f", if (fscanf(fp, "%f %f %f",
@ -750,7 +695,6 @@ void panel_update_system_stats(void)
} }
} }
/* Calculate stats width without rendering */
static int panel_calculate_stats_width(void) static int panel_calculate_stats_width(void)
{ {
if (dwn->xft_font == NULL && dwn->font == NULL) return 0; if (dwn->xft_font == NULL && dwn->font == NULL) return 0;
@ -758,11 +702,9 @@ static int panel_calculate_stats_width(void)
char buf[256]; char buf[256];
int total = 0; int total = 0;
/* CPU */
int len = snprintf(buf, sizeof(buf), "CPU:%2d%%", sys_stats.cpu_percent); int len = snprintf(buf, sizeof(buf), "CPU:%2d%%", sys_stats.cpu_percent);
total += panel_text_width(buf, len) + WIDGET_SPACING; total += panel_text_width(buf, len) + WIDGET_SPACING;
/* Memory */
if (sys_stats.mem_total_mb >= 1024) { if (sys_stats.mem_total_mb >= 1024) {
len = snprintf(buf, sizeof(buf), "MEM:%.1fG/%dG", len = snprintf(buf, sizeof(buf), "MEM:%.1fG/%dG",
sys_stats.mem_used_mb / 1024.0f, sys_stats.mem_total_mb / 1024); sys_stats.mem_used_mb / 1024.0f, sys_stats.mem_total_mb / 1024);
@ -772,12 +714,10 @@ static int panel_calculate_stats_width(void)
} }
total += panel_text_width(buf, len) + WIDGET_SPACING; total += panel_text_width(buf, len) + WIDGET_SPACING;
/* Load */
len = snprintf(buf, sizeof(buf), "Load:%.2f %.2f %.2f", len = snprintf(buf, sizeof(buf), "Load:%.2f %.2f %.2f",
sys_stats.load_1min, sys_stats.load_5min, sys_stats.load_15min); sys_stats.load_1min, sys_stats.load_5min, sys_stats.load_15min);
total += panel_text_width(buf, len) + WIDGET_SPACING; total += panel_text_width(buf, len) + WIDGET_SPACING;
/* Battery - use thread-safe snapshot */
BatteryState bat_snap = systray_get_battery_snapshot(); BatteryState bat_snap = systray_get_battery_snapshot();
if (bat_snap.present) { if (bat_snap.present) {
const char *bat_icon = bat_snap.charging ? "[+]" : "[=]"; const char *bat_icon = bat_snap.charging ? "[+]" : "[=]";
@ -803,24 +743,21 @@ static void panel_render_system_stats(Panel *panel, int x, int *width)
int text_y = panel_text_y(panel->height); int text_y = panel_text_y(panel->height);
int current_x = x; int current_x = x;
/* Format: "CPU: 25% | MEM: 4.2G/16G | Load: 1.23 0.98 0.76 | BAT: 85%" */
char stats_buf[256]; char stats_buf[256];
int len; int len;
/* CPU with color coding */
unsigned long cpu_color = colors->panel_fg; unsigned long cpu_color = colors->panel_fg;
if (sys_stats.cpu_percent >= 90) { if (sys_stats.cpu_percent >= 90) {
cpu_color = colors->workspace_urgent; /* Red for high CPU */ cpu_color = colors->workspace_urgent;
} else if (sys_stats.cpu_percent >= 70) { } else if (sys_stats.cpu_percent >= 70) {
cpu_color = 0xFFA500; /* Orange for medium-high */ cpu_color = 0xFFA500;
} }
len = snprintf(stats_buf, sizeof(stats_buf), "CPU:%2d%%", sys_stats.cpu_percent); len = snprintf(stats_buf, sizeof(stats_buf), "CPU:%2d%%", sys_stats.cpu_percent);
panel_draw_text(panel->buffer, current_x, text_y, stats_buf, len, cpu_color); panel_draw_text(panel->buffer, current_x, text_y, stats_buf, len, cpu_color);
current_x += panel_text_width(stats_buf, len) + WIDGET_SPACING; current_x += panel_text_width(stats_buf, len) + WIDGET_SPACING;
/* Memory */
unsigned long mem_color = colors->panel_fg; unsigned long mem_color = colors->panel_fg;
if (sys_stats.mem_percent >= 90) { if (sys_stats.mem_percent >= 90) {
mem_color = colors->workspace_urgent; mem_color = colors->workspace_urgent;
@ -838,7 +775,6 @@ static void panel_render_system_stats(Panel *panel, int x, int *width)
panel_draw_text(panel->buffer, current_x, text_y, stats_buf, len, mem_color); panel_draw_text(panel->buffer, current_x, text_y, stats_buf, len, mem_color);
current_x += panel_text_width(stats_buf, len) + WIDGET_SPACING; current_x += panel_text_width(stats_buf, len) + WIDGET_SPACING;
/* Load average */
unsigned long load_color = colors->panel_fg; unsigned long load_color = colors->panel_fg;
if (sys_stats.load_1min >= 4.0f) { if (sys_stats.load_1min >= 4.0f) {
load_color = colors->workspace_urgent; load_color = colors->workspace_urgent;
@ -851,16 +787,15 @@ 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); panel_draw_text(panel->buffer, current_x, text_y, stats_buf, len, load_color);
current_x += panel_text_width(stats_buf, len) + WIDGET_SPACING; current_x += panel_text_width(stats_buf, len) + WIDGET_SPACING;
/* Battery (if present) - use thread-safe snapshot */
BatteryState bat_snap = systray_get_battery_snapshot(); BatteryState bat_snap = systray_get_battery_snapshot();
if (bat_snap.present) { if (bat_snap.present) {
unsigned long bat_color = colors->panel_fg; unsigned long bat_color = colors->panel_fg;
if (bat_snap.percentage <= 20 && !bat_snap.charging) { if (bat_snap.percentage <= 20 && !bat_snap.charging) {
bat_color = colors->workspace_urgent; /* Red for low */ bat_color = colors->workspace_urgent;
} else if (bat_snap.percentage <= 40 && !bat_snap.charging) { } else if (bat_snap.percentage <= 40 && !bat_snap.charging) {
bat_color = 0xFFA500; /* Orange for medium-low */ bat_color = 0xFFA500;
} else if (bat_snap.charging) { } else if (bat_snap.charging) {
bat_color = colors->workspace_active; /* Blue for charging */ bat_color = colors->workspace_active;
} }
const char *bat_icon = bat_snap.charging ? "[+]" : "[=]"; const char *bat_icon = bat_snap.charging ? "[+]" : "[=]";
@ -872,11 +807,9 @@ static void panel_render_system_stats(Panel *panel, int x, int *width)
*width = current_x - x; *width = current_x - x;
} }
/* ========== System tray ========== */
void panel_init_systray(void) void panel_init_systray(void)
{ {
/* System tray initialization - requires _NET_SYSTEM_TRAY protocol */
LOG_DEBUG("System tray initialization (placeholder)"); LOG_DEBUG("System tray initialization (placeholder)");
} }

View File

@ -17,17 +17,15 @@
#include <dirent.h> #include <dirent.h>
#include <pthread.h> #include <pthread.h>
/* UTF-8 Icons for system tray */ #define ICON_WIFI_FULL "\xE2\x96\x82\xE2\x96\x84\xE2\x96\x86\xE2\x96\x88"
#define ICON_WIFI_FULL "\xE2\x96\x82\xE2\x96\x84\xE2\x96\x86\xE2\x96\x88" /* Full signal bars */ #define ICON_WIFI_OFF "\xE2\x9C\x97"
#define ICON_WIFI_OFF "\xE2\x9C\x97" /* X mark */ #define ICON_VOLUME_HIGH "\xF0\x9F\x94\x8A"
#define ICON_VOLUME_HIGH "\xF0\x9F\x94\x8A" /* Speaker high */ #define ICON_VOLUME_MED "\xF0\x9F\x94\x89"
#define ICON_VOLUME_MED "\xF0\x9F\x94\x89" /* Speaker medium */ #define ICON_VOLUME_LOW "\xF0\x9F\x94\x88"
#define ICON_VOLUME_LOW "\xF0\x9F\x94\x88" /* Speaker low */ #define ICON_VOLUME_MUTE "\xF0\x9F\x94\x87"
#define ICON_VOLUME_MUTE "\xF0\x9F\x94\x87" /* Speaker muted */ #define ICON_BATTERY_FULL "\xF0\x9F\x94\x8B"
#define ICON_BATTERY_FULL "\xF0\x9F\x94\x8B" /* Battery full */ #define ICON_BATTERY_CHARGE "\xE2\x9A\xA1"
#define ICON_BATTERY_CHARGE "\xE2\x9A\xA1" /* Lightning bolt */
/* Simple ASCII fallback icons */
#define ASCII_WIFI_ON "W" #define ASCII_WIFI_ON "W"
#define ASCII_WIFI_OFF "-" #define ASCII_WIFI_OFF "-"
#define ASCII_VOL_HIGH "V" #define ASCII_VOL_HIGH "V"
@ -35,7 +33,6 @@
#define ASCII_BATTERY "B" #define ASCII_BATTERY "B"
#define ASCII_CHARGING "+" #define ASCII_CHARGING "+"
/* Widget dimensions */
#define SYSTRAY_SPACING 12 #define SYSTRAY_SPACING 12
#define DROPDOWN_ITEM_HEIGHT 28 #define DROPDOWN_ITEM_HEIGHT 28
#define DROPDOWN_WIDTH 400 #define DROPDOWN_WIDTH 400
@ -45,32 +42,26 @@
#define SLIDER_PADDING 8 #define SLIDER_PADDING 8
#define SLIDER_KNOB_HEIGHT 8 #define SLIDER_KNOB_HEIGHT 8
/* Scan interval in milliseconds */
#define WIFI_SCAN_INTERVAL 10000 #define WIFI_SCAN_INTERVAL 10000
/* Global state */
WifiState wifi_state = {0}; WifiState wifi_state = {0};
AudioState audio_state = {0}; AudioState audio_state = {0};
BatteryState battery_state = {0}; BatteryState battery_state = {0};
DropdownMenu *wifi_menu = NULL; DropdownMenu *wifi_menu = NULL;
VolumeSlider *volume_slider = NULL; VolumeSlider *volume_slider = NULL;
/* Async update thread */
static pthread_t update_thread; static pthread_t update_thread;
static pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER;
static volatile int thread_running = 0; static volatile int thread_running = 0;
/* Systray position tracking */
static int systray_x = 0; static int systray_x = 0;
static int systray_width = 0; static int systray_width = 0;
static int wifi_icon_x = 0; static int wifi_icon_x = 0;
static int audio_icon_x = 0; static int audio_icon_x = 0;
/* Forward declarations for internal update functions */
static void wifi_update_state_internal(void); static void wifi_update_state_internal(void);
static void audio_update_state_internal(void); static void audio_update_state_internal(void);
/* ========== UTF-8 Text Drawing ========== */
static void draw_utf8_text(Drawable d, int x, int y, const char *text, unsigned long color) static void draw_utf8_text(Drawable d, int x, int y, const char *text, unsigned long color)
{ {
@ -79,7 +70,6 @@ static void draw_utf8_text(Drawable d, int x, int y, const char *text, unsigned
} }
if (dwn->xft_font != NULL) { if (dwn->xft_font != NULL) {
/* Use Xft for proper UTF-8 rendering */
XftDraw *xft_draw = XftDrawCreate(dwn->display, d, XftDraw *xft_draw = XftDrawCreate(dwn->display, d,
DefaultVisual(dwn->display, dwn->screen), DefaultVisual(dwn->display, dwn->screen),
dwn->colormap); dwn->colormap);
@ -87,7 +77,6 @@ static void draw_utf8_text(Drawable d, int x, int y, const char *text, unsigned
XftColor xft_color; XftColor xft_color;
XRenderColor render_color; XRenderColor render_color;
/* Convert X11 color to XRender color */
render_color.red = ((color >> 16) & 0xFF) * 257; render_color.red = ((color >> 16) & 0xFF) * 257;
render_color.green = ((color >> 8) & 0xFF) * 257; render_color.green = ((color >> 8) & 0xFF) * 257;
render_color.blue = (color & 0xFF) * 257; render_color.blue = (color & 0xFF) * 257;
@ -108,7 +97,6 @@ static void draw_utf8_text(Drawable d, int x, int y, const char *text, unsigned
} }
} }
/* Fallback to basic X11 drawing */
XSetForeground(dwn->display, dwn->gc, color); XSetForeground(dwn->display, dwn->gc, color);
XDrawString(dwn->display, d, dwn->gc, x, y, text, strlen(text)); XDrawString(dwn->display, d, dwn->gc, x, y, text, strlen(text));
} }
@ -130,31 +118,25 @@ static int get_text_width(const char *text)
return XTextWidth(dwn->font, text, strlen(text)); return XTextWidth(dwn->font, text, strlen(text));
} }
return strlen(text) * 8; /* Rough estimate */ return strlen(text) * 8;
} }
/* ========== Initialization ========== */
/* Background thread for async state updates */
static void *systray_update_thread(void *arg) static void *systray_update_thread(void *arg)
{ {
(void)arg; (void)arg;
while (thread_running) { while (thread_running) {
/* Update WiFi state (slow - uses popen) */
wifi_update_state_internal(); wifi_update_state_internal();
/* Update audio state (slow - uses popen) */
audio_update_state_internal(); audio_update_state_internal();
/* Update battery state (fast - reads /sys files) */
pthread_mutex_lock(&state_mutex); pthread_mutex_lock(&state_mutex);
battery_update_state(); battery_update_state();
pthread_mutex_unlock(&state_mutex); pthread_mutex_unlock(&state_mutex);
/* Sleep for 2 seconds between updates */
for (int i = 0; i < 20 && thread_running; i++) { for (int i = 0; i < 20 && thread_running; i++) {
usleep(100000); /* 100ms chunks for responsive shutdown */ usleep(100000);
} }
} }
@ -167,11 +149,9 @@ void systray_init(void)
memset(&audio_state, 0, sizeof(audio_state)); memset(&audio_state, 0, sizeof(audio_state));
memset(&battery_state, 0, sizeof(battery_state)); memset(&battery_state, 0, sizeof(battery_state));
/* Set default states for instant display */
audio_state.volume = 50; audio_state.volume = 50;
wifi_state.enabled = true; wifi_state.enabled = true;
/* Start background update thread - all updates happen async */
thread_running = 1; thread_running = 1;
if (pthread_create(&update_thread, NULL, systray_update_thread, NULL) != 0) { if (pthread_create(&update_thread, NULL, systray_update_thread, NULL) != 0) {
LOG_WARN("Failed to create systray update thread"); LOG_WARN("Failed to create systray update thread");
@ -183,7 +163,6 @@ void systray_init(void)
void systray_cleanup(void) void systray_cleanup(void)
{ {
/* Stop the update thread */
if (thread_running) { if (thread_running) {
thread_running = 0; thread_running = 0;
pthread_join(update_thread, NULL); pthread_join(update_thread, NULL);
@ -199,7 +178,6 @@ void systray_cleanup(void)
} }
} }
/* ========== Battery Functions ========== */
void battery_update_state(void) void battery_update_state(void)
{ {
@ -213,7 +191,6 @@ void battery_update_state(void)
battery_state.charging = false; battery_state.charging = false;
battery_state.percentage = 0; battery_state.percentage = 0;
/* Look for battery in /sys/class/power_supply */
dir = opendir("/sys/class/power_supply"); dir = opendir("/sys/class/power_supply");
if (dir == NULL) { if (dir == NULL) {
return; return;
@ -224,7 +201,6 @@ void battery_update_state(void)
continue; continue;
} }
/* Check if this is a battery (not AC adapter) */
snprintf(path, sizeof(path), "/sys/class/power_supply/%s/type", entry->d_name); snprintf(path, sizeof(path), "/sys/class/power_supply/%s/type", entry->d_name);
fp = fopen(path, "r"); fp = fopen(path, "r");
if (fp != NULL) { if (fp != NULL) {
@ -233,7 +209,6 @@ void battery_update_state(void)
if (strcmp(value, "Battery") == 0) { if (strcmp(value, "Battery") == 0) {
battery_state.present = true; battery_state.present = true;
/* Get capacity */
snprintf(path, sizeof(path), "/sys/class/power_supply/%s/capacity", entry->d_name); snprintf(path, sizeof(path), "/sys/class/power_supply/%s/capacity", entry->d_name);
FILE *cap_fp = fopen(path, "r"); FILE *cap_fp = fopen(path, "r");
if (cap_fp != NULL) { if (cap_fp != NULL) {
@ -243,7 +218,6 @@ void battery_update_state(void)
fclose(cap_fp); fclose(cap_fp);
} }
/* Get status (charging/discharging) */
snprintf(path, sizeof(path), "/sys/class/power_supply/%s/status", entry->d_name); snprintf(path, sizeof(path), "/sys/class/power_supply/%s/status", entry->d_name);
FILE *status_fp = fopen(path, "r"); FILE *status_fp = fopen(path, "r");
if (status_fp != NULL) { if (status_fp != NULL) {
@ -256,7 +230,7 @@ void battery_update_state(void)
} }
fclose(fp); fclose(fp);
break; /* Found a battery, stop searching */ break;
} }
} }
fclose(fp); fclose(fp);
@ -268,7 +242,7 @@ void battery_update_state(void)
const char *battery_get_icon(void) const char *battery_get_icon(void)
{ {
if (!battery_state.present) { if (!battery_state.present) {
return ""; /* No battery = no icon */ return "";
} }
if (battery_state.charging) { if (battery_state.charging) {
@ -278,16 +252,13 @@ const char *battery_get_icon(void)
return ASCII_BATTERY; return ASCII_BATTERY;
} }
/* ========== WiFi Functions ========== */
/* Internal update function - called from background thread */
static void wifi_update_state_internal(void) static void wifi_update_state_internal(void)
{ {
FILE *fp; FILE *fp;
char line[256]; char line[256];
WifiState temp_state = {0}; WifiState temp_state = {0};
/* Check if WiFi is connected and get current SSID */
fp = popen("nmcli -t -f GENERAL.STATE,GENERAL.CONNECTION dev show 2>/dev/null | head -2", "r"); fp = popen("nmcli -t -f GENERAL.STATE,GENERAL.CONNECTION dev show 2>/dev/null | head -2", "r");
if (fp != NULL) { if (fp != NULL) {
while (fgets(line, sizeof(line), fp) != NULL) { while (fgets(line, sizeof(line), fp) != NULL) {
@ -310,7 +281,6 @@ static void wifi_update_state_internal(void)
pclose(fp); pclose(fp);
} }
/* Get signal strength if connected */
if (temp_state.connected) { if (temp_state.connected) {
fp = popen("nmcli -t -f IN-USE,SIGNAL dev wifi list 2>/dev/null | grep '^\\*' | cut -d: -f2", "r"); fp = popen("nmcli -t -f IN-USE,SIGNAL dev wifi list 2>/dev/null | grep '^\\*' | cut -d: -f2", "r");
if (fp != NULL) { if (fp != NULL) {
@ -321,7 +291,6 @@ static void wifi_update_state_internal(void)
} }
} }
/* Copy to global state with mutex protection */
pthread_mutex_lock(&state_mutex); pthread_mutex_lock(&state_mutex);
wifi_state.connected = temp_state.connected; wifi_state.connected = temp_state.connected;
wifi_state.enabled = temp_state.enabled; wifi_state.enabled = temp_state.enabled;
@ -330,11 +299,8 @@ static void wifi_update_state_internal(void)
pthread_mutex_unlock(&state_mutex); pthread_mutex_unlock(&state_mutex);
} }
/* Public function - now just reads cached state (non-blocking) */
void wifi_update_state(void) void wifi_update_state(void)
{ {
/* State is updated by background thread - this is now a no-op */
/* Kept for API compatibility */
} }
void wifi_scan_networks(void) void wifi_scan_networks(void)
@ -342,7 +308,6 @@ void wifi_scan_networks(void)
FILE *fp; FILE *fp;
char line[512]; char line[512];
/* Build network list in temporary storage first */
WifiNetwork temp_networks[MAX_WIFI_NETWORKS]; WifiNetwork temp_networks[MAX_WIFI_NETWORKS];
int temp_count = 0; int temp_count = 0;
long scan_time = get_time_ms(); long scan_time = get_time_ms();
@ -374,7 +339,6 @@ void wifi_scan_networks(void)
} }
pclose(fp); pclose(fp);
/* Copy to global state under lock */
pthread_mutex_lock(&state_mutex); pthread_mutex_lock(&state_mutex);
memcpy(wifi_state.networks, temp_networks, sizeof(temp_networks)); memcpy(wifi_state.networks, temp_networks, sizeof(temp_networks));
wifi_state.network_count = temp_count; wifi_state.network_count = temp_count;
@ -423,9 +387,7 @@ const char *wifi_get_icon(void)
return ASCII_WIFI_ON; return ASCII_WIFI_ON;
} }
/* ========== Audio Functions ========== */
/* Internal update function - called from background thread */
static void audio_update_state_internal(void) static void audio_update_state_internal(void)
{ {
FILE *fp; FILE *fp;
@ -449,18 +411,14 @@ static void audio_update_state_internal(void)
pclose(fp); pclose(fp);
} }
/* Copy to global state with mutex protection */
pthread_mutex_lock(&state_mutex); pthread_mutex_lock(&state_mutex);
audio_state.volume = volume; audio_state.volume = volume;
audio_state.muted = muted; audio_state.muted = muted;
pthread_mutex_unlock(&state_mutex); pthread_mutex_unlock(&state_mutex);
} }
/* Public function - now just reads cached state (non-blocking) */
void audio_update_state(void) void audio_update_state(void)
{ {
/* State is updated by background thread - this is now a no-op */
/* Kept for API compatibility */
} }
void audio_set_volume(int volume) void audio_set_volume(int volume)
@ -495,7 +453,6 @@ const char *audio_get_icon(void)
return ASCII_VOL_HIGH; return ASCII_VOL_HIGH;
} }
/* ========== Volume Slider ========== */
VolumeSlider *volume_slider_create(int x, int y) VolumeSlider *volume_slider_create(int x, int y)
{ {
@ -575,11 +532,9 @@ void volume_slider_render(VolumeSlider *slider)
Display *dpy = dwn->display; Display *dpy = dwn->display;
const ColorScheme *colors = config_get_colors(); const ColorScheme *colors = config_get_colors();
/* Clear background */
XSetForeground(dpy, dwn->gc, colors->panel_bg); XSetForeground(dpy, dwn->gc, colors->panel_bg);
XFillRectangle(dpy, slider->window, dwn->gc, 0, 0, slider->width, slider->height); XFillRectangle(dpy, slider->window, dwn->gc, 0, 0, slider->width, slider->height);
/* Draw track */
int track_x = slider->width / 2 - 2; int track_x = slider->width / 2 - 2;
int track_y = SLIDER_PADDING; int track_y = SLIDER_PADDING;
int track_height = slider->height - 2 * SLIDER_PADDING; int track_height = slider->height - 2 * SLIDER_PADDING;
@ -587,14 +542,12 @@ void volume_slider_render(VolumeSlider *slider)
XSetForeground(dpy, dwn->gc, colors->workspace_inactive); XSetForeground(dpy, dwn->gc, colors->workspace_inactive);
XFillRectangle(dpy, slider->window, dwn->gc, track_x, track_y, 4, track_height); XFillRectangle(dpy, slider->window, dwn->gc, track_x, track_y, 4, track_height);
/* Draw filled portion */
int fill_height = (audio_state.volume * track_height) / 100; int fill_height = (audio_state.volume * track_height) / 100;
int fill_y = track_y + track_height - fill_height; int fill_y = track_y + track_height - fill_height;
XSetForeground(dpy, dwn->gc, colors->workspace_active); XSetForeground(dpy, dwn->gc, colors->workspace_active);
XFillRectangle(dpy, slider->window, dwn->gc, track_x, fill_y, 4, fill_height); XFillRectangle(dpy, slider->window, dwn->gc, track_x, fill_y, 4, fill_height);
/* Draw knob */
int knob_y = fill_y - SLIDER_KNOB_HEIGHT / 2; int knob_y = fill_y - SLIDER_KNOB_HEIGHT / 2;
if (knob_y < track_y) knob_y = track_y; if (knob_y < track_y) knob_y = track_y;
if (knob_y > track_y + track_height - SLIDER_KNOB_HEIGHT) { if (knob_y > track_y + track_height - SLIDER_KNOB_HEIGHT) {
@ -605,7 +558,6 @@ void volume_slider_render(VolumeSlider *slider)
XFillRectangle(dpy, slider->window, dwn->gc, XFillRectangle(dpy, slider->window, dwn->gc,
track_x - 4, knob_y, 12, SLIDER_KNOB_HEIGHT); track_x - 4, knob_y, 12, SLIDER_KNOB_HEIGHT);
/* Draw percentage text at bottom */
char vol_text[8]; char vol_text[8];
snprintf(vol_text, sizeof(vol_text), "%d%%", audio_state.volume); snprintf(vol_text, sizeof(vol_text), "%d%%", audio_state.volume);
int text_width = get_text_width(vol_text); int text_width = get_text_width(vol_text);
@ -626,7 +578,6 @@ void volume_slider_handle_click(VolumeSlider *slider, int x, int y)
slider->dragging = true; slider->dragging = true;
/* Calculate volume from y position */
int track_y = SLIDER_PADDING; int track_y = SLIDER_PADDING;
int track_height = slider->height - 2 * SLIDER_PADDING; int track_height = slider->height - 2 * SLIDER_PADDING;
@ -657,7 +608,6 @@ void volume_slider_handle_release(VolumeSlider *slider)
slider->dragging = false; slider->dragging = false;
} }
/* ========== Dropdown Menu ========== */
DropdownMenu *dropdown_create(int x, int y, int width) DropdownMenu *dropdown_create(int x, int y, int width)
{ {
@ -707,7 +657,6 @@ void dropdown_show(DropdownMenu *menu)
menu->item_count = 1; menu->item_count = 1;
} }
/* Calculate auto-width based on longest network name + signal */
int max_text_width = 0; int max_text_width = 0;
for (int i = 0; i < wifi_state.network_count; i++) { for (int i = 0; i < wifi_state.network_count; i++) {
WifiNetwork *net = &wifi_state.networks[i]; WifiNetwork *net = &wifi_state.networks[i];
@ -778,7 +727,6 @@ void dropdown_render(DropdownMenu *menu)
} }
int text_y_offset = (DROPDOWN_ITEM_HEIGHT + font_height) / 2; int text_y_offset = (DROPDOWN_ITEM_HEIGHT + font_height) / 2;
/* Clear background */
XSetForeground(dpy, dwn->gc, colors->panel_bg); XSetForeground(dpy, dwn->gc, colors->panel_bg);
XFillRectangle(dpy, menu->window, dwn->gc, 0, 0, menu->width, menu->height); XFillRectangle(dpy, menu->window, dwn->gc, 0, 0, menu->width, menu->height);
@ -790,7 +738,6 @@ void dropdown_render(DropdownMenu *menu)
WifiNetwork *net = &wifi_state.networks[i]; WifiNetwork *net = &wifi_state.networks[i];
int y = DROPDOWN_PADDING + i * DROPDOWN_ITEM_HEIGHT; int y = DROPDOWN_PADDING + i * DROPDOWN_ITEM_HEIGHT;
/* Highlight hovered item */
if (i == menu->hovered_item) { if (i == menu->hovered_item) {
XSetForeground(dpy, dwn->gc, colors->workspace_active); XSetForeground(dpy, dwn->gc, colors->workspace_active);
XFillRectangle(dpy, menu->window, dwn->gc, XFillRectangle(dpy, menu->window, dwn->gc,
@ -799,7 +746,6 @@ void dropdown_render(DropdownMenu *menu)
unsigned long text_color = (i == menu->hovered_item) ? colors->panel_bg : colors->panel_fg; unsigned long text_color = (i == menu->hovered_item) ? colors->panel_bg : colors->panel_fg;
/* Network name */
char label[80]; char label[80];
if (net->connected) { if (net->connected) {
snprintf(label, sizeof(label), "> %s", net->ssid); snprintf(label, sizeof(label), "> %s", net->ssid);
@ -807,7 +753,6 @@ void dropdown_render(DropdownMenu *menu)
snprintf(label, sizeof(label), " %s", net->ssid); snprintf(label, sizeof(label), " %s", net->ssid);
} }
/* Truncate if needed */
if (strlen(label) > 25) { if (strlen(label) > 25) {
label[22] = '.'; label[22] = '.';
label[23] = '.'; label[23] = '.';
@ -817,7 +762,6 @@ void dropdown_render(DropdownMenu *menu)
draw_utf8_text(menu->window, DROPDOWN_PADDING, y + text_y_offset, label, text_color); draw_utf8_text(menu->window, DROPDOWN_PADDING, y + text_y_offset, label, text_color);
/* Signal strength */
char signal[8]; char signal[8];
snprintf(signal, sizeof(signal), "%d%%", net->signal); snprintf(signal, sizeof(signal), "%d%%", net->signal);
int signal_width = get_text_width(signal); int signal_width = get_text_width(signal);
@ -885,7 +829,6 @@ void dropdown_handle_motion(DropdownMenu *menu, int x, int y)
} }
} }
/* ========== System Tray Rendering ========== */
int systray_get_width(void) int systray_get_width(void)
{ {
@ -922,7 +865,6 @@ void systray_render(Panel *panel, int x, int *width)
const ColorScheme *colors = config_get_colors(); const ColorScheme *colors = config_get_colors();
/* Take thread-safe snapshots of state */
pthread_mutex_lock(&state_mutex); pthread_mutex_lock(&state_mutex);
AudioState audio_snap = audio_state; AudioState audio_snap = audio_state;
WifiState wifi_snap; WifiState wifi_snap;
@ -943,7 +885,6 @@ void systray_render(Panel *panel, int x, int *width)
systray_x = x; systray_x = x;
int current_x = x; int current_x = x;
/* Audio indicator */
audio_icon_x = current_x; audio_icon_x = current_x;
char audio_label[32]; char audio_label[32];
const char *audio_icon = (audio_snap.muted || audio_snap.volume == 0) ? ASCII_VOL_MUTE : ASCII_VOL_HIGH; const char *audio_icon = (audio_snap.muted || audio_snap.volume == 0) ? ASCII_VOL_MUTE : ASCII_VOL_HIGH;
@ -953,7 +894,6 @@ void systray_render(Panel *panel, int x, int *width)
draw_utf8_text(panel->buffer, current_x, text_y, audio_label, audio_color); draw_utf8_text(panel->buffer, current_x, text_y, audio_label, audio_color);
current_x += get_text_width(audio_label) + SYSTRAY_SPACING; current_x += get_text_width(audio_label) + SYSTRAY_SPACING;
/* WiFi indicator - show SSID and signal strength */
wifi_icon_x = current_x; wifi_icon_x = current_x;
char wifi_label[128]; char wifi_label[128];
const char *wifi_icon_str = (!wifi_snap.enabled || !wifi_snap.connected) ? ASCII_WIFI_OFF : ASCII_WIFI_ON; const char *wifi_icon_str = (!wifi_snap.enabled || !wifi_snap.connected) ? ASCII_WIFI_OFF : ASCII_WIFI_ON;
@ -976,10 +916,10 @@ void systray_render(Panel *panel, int x, int *width)
int systray_hit_test(int x) int systray_hit_test(int x)
{ {
if (x >= wifi_icon_x) { if (x >= wifi_icon_x) {
return 0; /* WiFi - extends to end of systray */ return 0;
} }
if (x >= audio_icon_x && x < wifi_icon_x) { if (x >= audio_icon_x && x < wifi_icon_x) {
return 1; /* Audio */ return 1;
} }
return -1; return -1;
} }
@ -988,8 +928,7 @@ void systray_handle_click(int x, int y, int button)
{ {
int widget = systray_hit_test(x); int widget = systray_hit_test(x);
if (widget == 0) { /* WiFi clicked */ if (widget == 0) {
/* Hide volume slider if open */
if (volume_slider != NULL && volume_slider->visible) { if (volume_slider != NULL && volume_slider->visible) {
volume_slider_hide(volume_slider); volume_slider_hide(volume_slider);
} }
@ -1012,14 +951,12 @@ void systray_handle_click(int x, int y, int button)
wifi_disconnect(); wifi_disconnect();
} }
} }
} else if (widget == 1) { /* Audio clicked */ } else if (widget == 1) {
/* Hide wifi menu if open */
if (wifi_menu != NULL && wifi_menu->visible) { if (wifi_menu != NULL && wifi_menu->visible) {
dropdown_hide(wifi_menu); dropdown_hide(wifi_menu);
} }
if (button == 1) { if (button == 1) {
/* Show volume slider */
if (volume_slider == NULL) { if (volume_slider == NULL) {
int panel_height = config_get_panel_height(); int panel_height = config_get_panel_height();
volume_slider = volume_slider_create(audio_icon_x, panel_height); volume_slider = volume_slider_create(audio_icon_x, panel_height);
@ -1033,18 +970,15 @@ void systray_handle_click(int x, int y, int button)
volume_slider_show(volume_slider); volume_slider_show(volume_slider);
} }
} else if (button == 3) { } else if (button == 3) {
/* Right-click: toggle mute */
audio_toggle_mute(); audio_toggle_mute();
panel_render_all(); panel_render_all();
} else if (button == 4) { } else if (button == 4) {
/* Scroll up: increase volume */
audio_set_volume(audio_state.volume + 5); audio_set_volume(audio_state.volume + 5);
if (volume_slider != NULL && volume_slider->visible) { if (volume_slider != NULL && volume_slider->visible) {
volume_slider_render(volume_slider); volume_slider_render(volume_slider);
} }
panel_render_all(); panel_render_all();
} else if (button == 5) { } else if (button == 5) {
/* Scroll down: decrease volume */
audio_set_volume(audio_state.volume - 5); audio_set_volume(audio_state.volume - 5);
if (volume_slider != NULL && volume_slider->visible) { if (volume_slider != NULL && volume_slider->visible) {
volume_slider_render(volume_slider); volume_slider_render(volume_slider);
@ -1058,10 +992,7 @@ void systray_handle_click(int x, int y, int button)
void systray_update(void) void systray_update(void)
{ {
/* State is updated asynchronously by the background thread.
* This function now only handles the WiFi menu re-scan. */
/* Re-scan WiFi networks periodically if menu is open */
if (wifi_menu != NULL && wifi_menu->visible) { if (wifi_menu != NULL && wifi_menu->visible) {
long now = get_time_ms(); long now = get_time_ms();
pthread_mutex_lock(&state_mutex); pthread_mutex_lock(&state_mutex);
@ -1075,7 +1006,6 @@ void systray_update(void)
} }
} }
/* ========== Thread-safe access functions ========== */
void systray_lock(void) void systray_lock(void)
{ {

View File

@ -21,30 +21,27 @@
#include <stdatomic.h> #include <stdatomic.h>
#include <fcntl.h> #include <fcntl.h>
/* ========== Async Logging Configuration ========== */
#define LOG_RING_SIZE 256 /* Number of log entries in ring buffer */ #define LOG_RING_SIZE 256
#define LOG_MSG_MAX_LEN 512 /* Max length of a single log message */ #define LOG_MSG_MAX_LEN 512
#define LOG_MAX_FILE_SIZE (5 * 1024 * 1024) /* 5 MB max log file size */ #define LOG_MAX_FILE_SIZE (5 * 1024 * 1024)
#define LOG_FLUSH_INTERVAL_MS 100 /* Flush to disk every 100ms */ #define LOG_FLUSH_INTERVAL_MS 100
/* Log entry in ring buffer */
typedef struct { typedef struct {
char message[LOG_MSG_MAX_LEN]; char message[LOG_MSG_MAX_LEN];
LogLevel level; LogLevel level;
_Atomic int ready; /* 0 = empty, 1 = ready to write */ _Atomic int ready;
} LogEntry; } LogEntry;
/* Static state for async logging */
static LogEntry log_ring[LOG_RING_SIZE]; static LogEntry log_ring[LOG_RING_SIZE];
static _Atomic size_t log_write_idx = 0; /* Next slot to write to */ static _Atomic size_t log_write_idx = 0;
static _Atomic size_t log_read_idx = 0; /* Next slot to read from */ static _Atomic size_t log_read_idx = 0;
static _Atomic int log_running = 0; /* Log thread running flag */ static _Atomic int log_running = 0;
static pthread_t log_thread; static pthread_t log_thread;
static int log_fd = -1; /* File descriptor for log file */ static int log_fd = -1;
static char *log_path = NULL; /* Path to log file */ static char *log_path = NULL;
static LogLevel min_level = LOG_INFO; static LogLevel min_level = LOG_INFO;
static _Atomic size_t log_file_size = 0; /* Current log file size */ static _Atomic size_t log_file_size = 0;
static const char *level_names[] = { static const char *level_names[] = {
"DEBUG", "DEBUG",
@ -54,19 +51,16 @@ static const char *level_names[] = {
}; };
static const char *level_colors[] = { static const char *level_colors[] = {
"\033[36m", /* Cyan for DEBUG */ "\033[36m",
"\033[32m", /* Green for INFO */ "\033[32m",
"\033[33m", /* Yellow for WARN */ "\033[33m",
"\033[31m" /* Red for ERROR */ "\033[31m"
}; };
/* Forward declarations for internal log functions */
static void log_rotate_if_needed(void); static void log_rotate_if_needed(void);
static void *log_writer_thread(void *arg); static void *log_writer_thread(void *arg);
/* ========== Async Logging Implementation ========== */
/* Rotate log file if it exceeds max size */
static void log_rotate_if_needed(void) static void log_rotate_if_needed(void)
{ {
if (log_fd < 0 || log_path == NULL) { if (log_fd < 0 || log_path == NULL) {
@ -78,27 +72,22 @@ static void log_rotate_if_needed(void)
return; return;
} }
/* Close current file */
close(log_fd); close(log_fd);
log_fd = -1; log_fd = -1;
/* Create backup filename */
size_t path_len = strlen(log_path); size_t path_len = strlen(log_path);
char *backup_path = malloc(path_len + 8); char *backup_path = malloc(path_len + 8);
if (backup_path != NULL) { if (backup_path != NULL) {
snprintf(backup_path, path_len + 8, "%s.old", log_path); snprintf(backup_path, path_len + 8, "%s.old", log_path);
/* Remove old backup if exists, rename current to backup */
unlink(backup_path); unlink(backup_path);
rename(log_path, backup_path); rename(log_path, backup_path);
free(backup_path); free(backup_path);
} }
/* Reopen fresh log file */
log_fd = open(log_path, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, 0644); log_fd = open(log_path, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, 0644);
atomic_store(&log_file_size, 0); atomic_store(&log_file_size, 0);
} }
/* Background thread that writes log entries to file */
static void *log_writer_thread(void *arg) static void *log_writer_thread(void *arg)
{ {
(void)arg; (void)arg;
@ -107,22 +96,18 @@ static void *log_writer_thread(void *arg)
size_t read_idx = atomic_load(&log_read_idx); size_t read_idx = atomic_load(&log_read_idx);
size_t write_idx = atomic_load(&log_write_idx); size_t write_idx = atomic_load(&log_write_idx);
/* Process all available entries */
while (read_idx != write_idx) { while (read_idx != write_idx) {
size_t idx = read_idx % LOG_RING_SIZE; size_t idx = read_idx % LOG_RING_SIZE;
LogEntry *entry = &log_ring[idx]; LogEntry *entry = &log_ring[idx];
/* Wait for entry to be ready (spin briefly) */
int attempts = 0; int attempts = 0;
while (!atomic_load(&entry->ready) && attempts < 100) { while (!atomic_load(&entry->ready) && attempts < 100) {
attempts++; attempts++;
/* Brief yield */ struct timespec ts = {0, 1000};
struct timespec ts = {0, 1000}; /* 1 microsecond */
nanosleep(&ts, NULL); nanosleep(&ts, NULL);
} }
if (atomic_load(&entry->ready)) { if (atomic_load(&entry->ready)) {
/* Write to file if open */
if (log_fd >= 0) { if (log_fd >= 0) {
log_rotate_if_needed(); log_rotate_if_needed();
if (log_fd >= 0) { if (log_fd >= 0) {
@ -133,7 +118,6 @@ static void *log_writer_thread(void *arg)
} }
} }
/* Mark entry as consumed */
atomic_store(&entry->ready, 0); atomic_store(&entry->ready, 0);
} }
@ -141,7 +125,6 @@ static void *log_writer_thread(void *arg)
atomic_store(&log_read_idx, read_idx); atomic_store(&log_read_idx, read_idx);
} }
/* Sleep before next check */
struct timespec ts = {0, LOG_FLUSH_INTERVAL_MS * 1000000L}; struct timespec ts = {0, LOG_FLUSH_INTERVAL_MS * 1000000L};
nanosleep(&ts, NULL); nanosleep(&ts, NULL);
} }
@ -151,7 +134,6 @@ static void *log_writer_thread(void *arg)
void log_init(const char *path) void log_init(const char *path)
{ {
/* Initialize ring buffer */
memset(log_ring, 0, sizeof(log_ring)); memset(log_ring, 0, sizeof(log_ring));
atomic_store(&log_write_idx, 0); atomic_store(&log_write_idx, 0);
atomic_store(&log_read_idx, 0); atomic_store(&log_read_idx, 0);
@ -159,42 +141,36 @@ void log_init(const char *path)
if (path != NULL) { if (path != NULL) {
char *expanded = expand_path(path); char *expanded = expand_path(path);
if (expanded != NULL) { if (expanded != NULL) {
/* Ensure directory exists */
char *dir = strdup(expanded); char *dir = strdup(expanded);
if (dir != NULL) { if (dir != NULL) {
char *last_slash = strrchr(dir, '/'); char *last_slash = strrchr(dir, '/');
if (last_slash != NULL) { if (last_slash != NULL) {
*last_slash = '\0'; *last_slash = '\0';
/* Create directory recursively using mkdir */
struct stat st; struct stat st;
if (stat(dir, &st) != 0) { if (stat(dir, &st) != 0) {
/* Directory doesn't exist, try to create it */
char cmd[512]; char cmd[512];
snprintf(cmd, sizeof(cmd), "mkdir -p '%s' 2>/dev/null", dir); snprintf(cmd, sizeof(cmd), "mkdir -p '%s' 2>/dev/null", dir);
int ret = system(cmd); int ret = system(cmd);
(void)ret; /* Ignore result - file open will fail if dir creation fails */ (void)ret;
} }
} }
free(dir); free(dir);
} }
/* Open log file with O_APPEND for atomic writes */
log_fd = open(expanded, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, 0644); log_fd = open(expanded, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, 0644);
if (log_fd < 0) { if (log_fd < 0) {
fprintf(stderr, "Warning: Could not open log file: %s\n", expanded); fprintf(stderr, "Warning: Could not open log file: %s\n", expanded);
} else { } else {
/* Get current file size */
struct stat st; struct stat st;
if (fstat(log_fd, &st) == 0) { if (fstat(log_fd, &st) == 0) {
atomic_store(&log_file_size, (size_t)st.st_size); atomic_store(&log_file_size, (size_t)st.st_size);
} }
} }
log_path = expanded; /* Keep for rotation */ log_path = expanded;
} }
} }
/* Start background writer thread */
atomic_store(&log_running, 1); atomic_store(&log_running, 1);
if (pthread_create(&log_thread, NULL, log_writer_thread, NULL) != 0) { if (pthread_create(&log_thread, NULL, log_writer_thread, NULL) != 0) {
fprintf(stderr, "Warning: Could not create log writer thread\n"); fprintf(stderr, "Warning: Could not create log writer thread\n");
@ -204,23 +180,19 @@ void log_init(const char *path)
void log_close(void) void log_close(void)
{ {
/* Signal thread to stop */
if (!atomic_load(&log_running)) { if (!atomic_load(&log_running)) {
goto cleanup; goto cleanup;
} }
atomic_store(&log_running, 0); atomic_store(&log_running, 0);
/* Wait for thread to finish - give it time to flush */
pthread_join(log_thread, NULL); pthread_join(log_thread, NULL);
cleanup: cleanup:
/* Close file */
if (log_fd >= 0) { if (log_fd >= 0) {
close(log_fd); close(log_fd);
log_fd = -1; log_fd = -1;
} }
/* Free path */
if (log_path != NULL) { if (log_path != NULL) {
free(log_path); free(log_path);
log_path = NULL; log_path = NULL;
@ -234,7 +206,6 @@ void log_set_level(LogLevel level)
void log_flush(void) void log_flush(void)
{ {
/* Force flush all pending log entries synchronously */
if (!atomic_load(&log_running) || log_fd < 0) { if (!atomic_load(&log_running) || log_fd < 0) {
return; return;
} }
@ -258,7 +229,6 @@ void log_flush(void)
atomic_store(&log_read_idx, read_idx); atomic_store(&log_read_idx, read_idx);
} }
/* Sync to disk */
fsync(log_fd); fsync(log_fd);
} }
@ -268,43 +238,35 @@ void log_msg(LogLevel level, const char *fmt, ...)
return; return;
} }
/* Get timestamp */
time_t now = time(NULL); time_t now = time(NULL);
struct tm tm_info; struct tm tm_info;
localtime_r(&now, &tm_info); /* Thread-safe version */ localtime_r(&now, &tm_info);
char time_buf[32]; char time_buf[32];
strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", &tm_info); strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", &tm_info);
/* Format the log message */ char msg_buf[LOG_MSG_MAX_LEN - 64];
char msg_buf[LOG_MSG_MAX_LEN - 64]; /* Leave room for prefix */
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
vsnprintf(msg_buf, sizeof(msg_buf), fmt, args); vsnprintf(msg_buf, sizeof(msg_buf), fmt, args);
va_end(args); va_end(args);
/* Print to stderr (always, for immediate visibility) */
fprintf(stderr, "%s[%s] %s: %s%s\n", fprintf(stderr, "%s[%s] %s: %s%s\n",
level_colors[level], time_buf, level_names[level], "\033[0m", msg_buf); level_colors[level], time_buf, level_names[level], "\033[0m", msg_buf);
/* Add to ring buffer for async file write (non-blocking) */
if (atomic_load(&log_running)) { if (atomic_load(&log_running)) {
size_t write_idx = atomic_fetch_add(&log_write_idx, 1); size_t write_idx = atomic_fetch_add(&log_write_idx, 1);
size_t idx = write_idx % LOG_RING_SIZE; size_t idx = write_idx % LOG_RING_SIZE;
LogEntry *entry = &log_ring[idx]; LogEntry *entry = &log_ring[idx];
/* Check if slot is available (not overwriting unread entry) */
/* If buffer is full, we drop the message rather than block */
if (!atomic_load(&entry->ready)) { if (!atomic_load(&entry->ready)) {
entry->level = level; entry->level = level;
snprintf(entry->message, sizeof(entry->message), snprintf(entry->message, sizeof(entry->message),
"[%s] %s: %s\n", time_buf, level_names[level], msg_buf); "[%s] %s: %s\n", time_buf, level_names[level], msg_buf);
atomic_store(&entry->ready, 1); atomic_store(&entry->ready, 1);
} }
/* If entry->ready is true, buffer is full - message is dropped (non-blocking) */
} }
} }
/* ========== Memory allocation ========== */
void *dwn_malloc(size_t size) void *dwn_malloc(size_t size)
{ {
@ -360,10 +322,8 @@ void secure_wipe(void *ptr, size_t size)
return; return;
} }
#if defined(__GLIBC__) && (__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 25) #if defined(__GLIBC__) && (__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 25)
/* Use explicit_bzero if available (glibc 2.25+) */
explicit_bzero(ptr, size); explicit_bzero(ptr, size);
#else #else
/* Fallback: Use volatile to prevent compiler optimization */
volatile unsigned char *p = (volatile unsigned char *)ptr; volatile unsigned char *p = (volatile unsigned char *)ptr;
while (size--) { while (size--) {
*p++ = 0; *p++ = 0;
@ -371,7 +331,6 @@ void secure_wipe(void *ptr, size_t size)
#endif #endif
} }
/* ========== String utilities ========== */
char *str_trim(char *str) char *str_trim(char *str)
{ {
@ -379,7 +338,6 @@ char *str_trim(char *str)
return NULL; return NULL;
} }
/* Trim leading whitespace */
while (isspace((unsigned char)*str)) { while (isspace((unsigned char)*str)) {
str++; str++;
} }
@ -388,7 +346,6 @@ char *str_trim(char *str)
return str; return str;
} }
/* Trim trailing whitespace */
char *end = str + strlen(str) - 1; char *end = str + strlen(str) - 1;
while (end > str && isspace((unsigned char)*end)) { while (end > str && isspace((unsigned char)*end)) {
end--; end--;
@ -450,13 +407,11 @@ char *shell_escape(const char *str)
return dwn_strdup(""); return dwn_strdup("");
} }
/* Count single quotes to determine buffer size */
size_t quotes = 0; size_t quotes = 0;
for (const char *p = str; *p; p++) { for (const char *p = str; *p; p++) {
if (*p == '\'') quotes++; if (*p == '\'') quotes++;
} }
/* Each single quote becomes '\'' (4 chars), plus 2 for surrounding quotes */
size_t len = strlen(str) + quotes * 3 + 3; size_t len = strlen(str) + quotes * 3 + 3;
char *escaped = dwn_malloc(len); char *escaped = dwn_malloc(len);
char *dst = escaped; char *dst = escaped;
@ -464,7 +419,6 @@ char *shell_escape(const char *str)
*dst++ = '\''; *dst++ = '\'';
for (const char *src = str; *src; src++) { for (const char *src = str; *src; src++) {
if (*src == '\'') { if (*src == '\'') {
/* Close quote, add escaped quote, reopen quote: '\'' */
*dst++ = '\''; *dst++ = '\'';
*dst++ = '\\'; *dst++ = '\\';
*dst++ = '\''; *dst++ = '\'';
@ -479,7 +433,6 @@ char *shell_escape(const char *str)
return escaped; return escaped;
} }
/* ========== File utilities ========== */
bool file_exists(const char *path) bool file_exists(const char *path)
{ {
@ -548,7 +501,6 @@ char *expand_path(const char *path)
return dwn_strdup(path); return dwn_strdup(path);
} }
/* ========== Color utilities ========== */
unsigned long parse_color(const char *color_str) unsigned long parse_color(const char *color_str)
{ {
@ -561,7 +513,6 @@ unsigned long parse_color(const char *color_str)
return color.pixel; return color.pixel;
} }
/* Try parsing as hex */
if (color_str[0] == '#') { if (color_str[0] == '#') {
unsigned int r, g, b; unsigned int r, g, b;
if (sscanf(color_str + 1, "%02x%02x%02x", &r, &g, &b) == 3) { if (sscanf(color_str + 1, "%02x%02x%02x", &r, &g, &b) == 3) {
@ -589,7 +540,6 @@ void color_to_rgb(unsigned long color, int *r, int *g, int *b)
*b = xc.blue >> 8; *b = xc.blue >> 8;
} }
/* ========== Time utilities ========== */
long get_time_ms(void) long get_time_ms(void)
{ {
@ -606,7 +556,6 @@ void sleep_ms(int ms)
nanosleep(&ts, NULL); nanosleep(&ts, NULL);
} }
/* ========== Process utilities ========== */
int spawn(const char *cmd) int spawn(const char *cmd)
{ {
@ -618,7 +567,6 @@ int spawn(const char *cmd)
pid_t pid = fork(); pid_t pid = fork();
if (pid == 0) { if (pid == 0) {
/* Child process */
setsid(); setsid();
execl("/bin/sh", "sh", "-c", cmd, NULL); execl("/bin/sh", "sh", "-c", cmd, NULL);
_exit(EXIT_FAILURE); _exit(EXIT_FAILURE);
@ -627,7 +575,6 @@ int spawn(const char *cmd)
return -1; return -1;
} }
/* Wait for child */
int status; int status;
waitpid(pid, &status, 0); waitpid(pid, &status, 0);
return WEXITSTATUS(status); return WEXITSTATUS(status);
@ -643,10 +590,8 @@ int spawn_async(const char *cmd)
pid_t pid = fork(); pid_t pid = fork();
if (pid == 0) { if (pid == 0) {
/* Child process */
setsid(); setsid();
/* Double fork to avoid zombies */
pid_t pid2 = fork(); pid_t pid2 = fork();
if (pid2 == 0) { if (pid2 == 0) {
execl("/bin/sh", "sh", "-c", cmd, NULL); execl("/bin/sh", "sh", "-c", cmd, NULL);
@ -658,7 +603,6 @@ int spawn_async(const char *cmd)
return -1; return -1;
} }
/* Wait for first child (which exits immediately) */
int status; int status;
waitpid(pid, &status, 0); waitpid(pid, &status, 0);
return 0; return 0;
@ -678,7 +622,6 @@ char *spawn_capture(const char *cmd)
return NULL; return NULL;
} }
/* Read output */
size_t buf_size = 1024; size_t buf_size = 1024;
size_t len = 0; size_t len = 0;
char *output = dwn_malloc(buf_size); char *output = dwn_malloc(buf_size);
@ -691,7 +634,6 @@ char *spawn_capture(const char *cmd)
buf_size *= 2; buf_size *= 2;
output = dwn_realloc(output, buf_size); output = dwn_realloc(output, buf_size);
} }
/* Use memcpy with explicit bounds instead of strcpy */
memcpy(output + len, line, line_len + 1); memcpy(output + len, line, line_len + 1);
len += line_len; len += line_len;
} }
@ -701,7 +643,6 @@ char *spawn_capture(const char *cmd)
LOG_DEBUG("Command exited with status %d", status); LOG_DEBUG("Command exited with status %d", status);
} }
/* Trim trailing newline */
if (len > 0 && output[len - 1] == '\n') { if (len > 0 && output[len - 1] == '\n') {
output[len - 1] = '\0'; output[len - 1] = '\0';
} }

View File

@ -14,7 +14,6 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
/* ========== Initialization ========== */
void workspace_init(void) void workspace_init(void)
{ {
@ -36,7 +35,6 @@ void workspace_init(void)
ws->master_count = (dwn->config != NULL) ? ws->master_count = (dwn->config != NULL) ?
dwn->config->default_master_count : 1; dwn->config->default_master_count : 1;
/* Default names: "1", "2", ..., "9" */
snprintf(ws->name, sizeof(ws->name), "%d", i + 1); snprintf(ws->name, sizeof(ws->name), "%d", i + 1);
} }
@ -45,10 +43,8 @@ void workspace_init(void)
void workspace_cleanup(void) void workspace_cleanup(void)
{ {
/* Clients are cleaned up separately */
} }
/* ========== Workspace access ========== */
Workspace *workspace_get(int index) Workspace *workspace_get(int index)
{ {
@ -68,7 +64,6 @@ int workspace_get_current_index(void)
return dwn != NULL ? dwn->current_workspace : 0; return dwn != NULL ? dwn->current_workspace : 0;
} }
/* ========== Workspace switching ========== */
void workspace_switch(int index) void workspace_switch(int index)
{ {
@ -83,23 +78,17 @@ void workspace_switch(int index)
LOG_DEBUG("Switching from workspace %d to %d", LOG_DEBUG("Switching from workspace %d to %d",
dwn->current_workspace + 1, index + 1); dwn->current_workspace + 1, index + 1);
/* Grab server to batch all X operations - prevents flickering and improves speed */
XGrabServer(dwn->display); XGrabServer(dwn->display);
/* Hide current workspace windows */
workspace_hide(dwn->current_workspace); workspace_hide(dwn->current_workspace);
/* Update current workspace */
int old_workspace = dwn->current_workspace; int old_workspace = dwn->current_workspace;
dwn->current_workspace = index; dwn->current_workspace = index;
/* Show new workspace windows */
workspace_show(index); workspace_show(index);
/* Arrange windows on new workspace */
workspace_arrange(index); workspace_arrange(index);
/* Focus appropriate window */
Workspace *ws = workspace_get(index); Workspace *ws = workspace_get(index);
if (ws != NULL) { if (ws != NULL) {
if (ws->focused != NULL) { if (ws->focused != NULL) {
@ -109,17 +98,14 @@ void workspace_switch(int index)
if (first != NULL) { if (first != NULL) {
client_focus(first); client_focus(first);
} else { } else {
/* No windows, focus root */
XSetInputFocus(dwn->display, dwn->root, RevertToPointerRoot, CurrentTime); XSetInputFocus(dwn->display, dwn->root, RevertToPointerRoot, CurrentTime);
atoms_set_active_window(None); atoms_set_active_window(None);
} }
} }
} }
/* Update EWMH */
atoms_set_current_desktop(index); atoms_set_current_desktop(index);
/* Release server and sync - all changes appear atomically */
XUngrabServer(dwn->display); XUngrabServer(dwn->display);
XSync(dwn->display, False); XSync(dwn->display, False);
@ -138,7 +124,6 @@ void workspace_switch_prev(void)
workspace_switch(prev); workspace_switch(prev);
} }
/* ========== Client management ========== */
void workspace_add_client(int workspace, Client *client) void workspace_add_client(int workspace, Client *client)
{ {
@ -151,11 +136,8 @@ void workspace_add_client(int workspace, Client *client)
return; return;
} }
/* Add to workspace client list (at head) */
client->workspace = workspace; client->workspace = workspace;
/* We don't maintain a separate linked list per workspace,
just use the client's workspace field */
} }
void workspace_remove_client(int workspace, Client *client) void workspace_remove_client(int workspace, Client *client)
@ -169,7 +151,6 @@ void workspace_remove_client(int workspace, Client *client)
return; return;
} }
/* If this was the focused client, clear it */
if (ws->focused == client) { if (ws->focused == client) {
ws->focused = NULL; ws->focused = NULL;
} }
@ -190,30 +171,23 @@ void workspace_move_client(Client *client, int new_workspace)
LOG_DEBUG("Moving window '%s' from workspace %d to %d", LOG_DEBUG("Moving window '%s' from workspace %d to %d",
client->title, old_workspace + 1, new_workspace + 1); client->title, old_workspace + 1, new_workspace + 1);
/* Grab server to batch all X operations */
XGrabServer(dwn->display); XGrabServer(dwn->display);
/* Remove from old workspace */
workspace_remove_client(old_workspace, client); workspace_remove_client(old_workspace, client);
/* Add to new workspace */
workspace_add_client(new_workspace, client); workspace_add_client(new_workspace, client);
/* Update EWMH */
atoms_set_window_desktop(client->window, new_workspace); atoms_set_window_desktop(client->window, new_workspace);
/* Hide if moving to non-current workspace */
if (new_workspace != dwn->current_workspace) { if (new_workspace != dwn->current_workspace) {
client_hide(client); client_hide(client);
} else { } else {
client_show(client); client_show(client);
} }
/* Rearrange both workspaces */
workspace_arrange(old_workspace); workspace_arrange(old_workspace);
workspace_arrange(new_workspace); workspace_arrange(new_workspace);
/* Release server and sync */
XUngrabServer(dwn->display); XUngrabServer(dwn->display);
XSync(dwn->display, False); XSync(dwn->display, False);
} }
@ -239,7 +213,6 @@ Client *workspace_get_focused_client(int workspace)
return ws != NULL ? ws->focused : NULL; return ws != NULL ? ws->focused : NULL;
} }
/* ========== Layout ========== */
void workspace_set_layout(int workspace, LayoutType layout) void workspace_set_layout(int workspace, LayoutType layout)
{ {
@ -250,7 +223,6 @@ void workspace_set_layout(int workspace, LayoutType layout)
ws->layout = layout; ws->layout = layout;
/* Batch the arrangement */
XGrabServer(dwn->display); XGrabServer(dwn->display);
workspace_arrange(workspace); workspace_arrange(workspace);
XUngrabServer(dwn->display); XUngrabServer(dwn->display);
@ -274,7 +246,6 @@ void workspace_cycle_layout(int workspace)
ws->layout = (ws->layout + 1) % LAYOUT_COUNT; ws->layout = (ws->layout + 1) % LAYOUT_COUNT;
/* Batch the arrangement for smoother transition */
XGrabServer(dwn->display); XGrabServer(dwn->display);
workspace_arrange(workspace); workspace_arrange(workspace);
XUngrabServer(dwn->display); XUngrabServer(dwn->display);
@ -291,13 +262,11 @@ void workspace_set_master_ratio(int workspace, float ratio)
return; return;
} }
/* Clamp ratio */
if (ratio < 0.1f) ratio = 0.1f; if (ratio < 0.1f) ratio = 0.1f;
if (ratio > 0.9f) ratio = 0.9f; if (ratio > 0.9f) ratio = 0.9f;
ws->master_ratio = ratio; ws->master_ratio = ratio;
/* Batch the arrangement */
XGrabServer(dwn->display); XGrabServer(dwn->display);
workspace_arrange(workspace); workspace_arrange(workspace);
XUngrabServer(dwn->display); XUngrabServer(dwn->display);
@ -322,7 +291,6 @@ void workspace_set_master_count(int workspace, int count)
if (count < 1) count = 1; if (count < 1) count = 1;
ws->master_count = count; ws->master_count = count;
/* Batch the arrangement */
XGrabServer(dwn->display); XGrabServer(dwn->display);
workspace_arrange(workspace); workspace_arrange(workspace);
XUngrabServer(dwn->display); XUngrabServer(dwn->display);
@ -337,7 +305,6 @@ void workspace_adjust_master_count(int workspace, int delta)
} }
} }
/* ========== Arrangement ========== */
void workspace_arrange(int workspace) void workspace_arrange(int workspace)
{ {
@ -346,7 +313,6 @@ void workspace_arrange(int workspace)
return; return;
} }
/* Only arrange visible workspace */
if (workspace != dwn->current_workspace) { if (workspace != dwn->current_workspace) {
return; return;
} }
@ -356,14 +322,12 @@ void workspace_arrange(int workspace)
void workspace_arrange_current(void) void workspace_arrange_current(void)
{ {
/* Batch the arrangement */
XGrabServer(dwn->display); XGrabServer(dwn->display);
workspace_arrange(dwn->current_workspace); workspace_arrange(dwn->current_workspace);
XUngrabServer(dwn->display); XUngrabServer(dwn->display);
XSync(dwn->display, False); XSync(dwn->display, False);
} }
/* ========== Visibility ========== */
void workspace_show(int workspace) void workspace_show(int workspace)
{ {
@ -391,7 +355,6 @@ void workspace_hide(int workspace)
} }
} }
/* ========== Properties ========== */
void workspace_set_name(int workspace, const char *name) void workspace_set_name(int workspace, const char *name)
{ {
@ -422,7 +385,6 @@ bool workspace_is_empty(int workspace)
return workspace_client_count(workspace) == 0; return workspace_client_count(workspace) == 0;
} }
/* ========== Focus cycling ========== */
void workspace_focus_next(void) void workspace_focus_next(void)
{ {
@ -431,13 +393,11 @@ void workspace_focus_next(void)
return; return;
} }
/* Find next client on same workspace */
Client *next = ws->focused->next; Client *next = ws->focused->next;
while (next != NULL && next->workspace != (unsigned int)dwn->current_workspace) { while (next != NULL && next->workspace != (unsigned int)dwn->current_workspace) {
next = next->next; next = next->next;
} }
/* Wrap around */
if (next == NULL) { if (next == NULL) {
next = workspace_get_first_client(dwn->current_workspace); next = workspace_get_first_client(dwn->current_workspace);
} }
@ -454,13 +414,11 @@ void workspace_focus_prev(void)
return; return;
} }
/* Find previous client on same workspace */
Client *prev = ws->focused->prev; Client *prev = ws->focused->prev;
while (prev != NULL && prev->workspace != (unsigned int)dwn->current_workspace) { while (prev != NULL && prev->workspace != (unsigned int)dwn->current_workspace) {
prev = prev->prev; prev = prev->prev;
} }
/* Wrap around */
if (prev == NULL) { if (prev == NULL) {
for (Client *c = client_get_last(); c != NULL; c = c->prev) { for (Client *c = client_get_last(); c != NULL; c = c->prev) {
if (c->workspace == (unsigned int)dwn->current_workspace) { if (c->workspace == (unsigned int)dwn->current_workspace) {