|
/*
|
|
* DWN - Desktop Window Manager
|
|
* retoor <retoor@molodetz.nl>
|
|
* Main entry point and event loop
|
|
*/
|
|
|
|
#include "dwn.h"
|
|
#include "config.h"
|
|
#include "atoms.h"
|
|
#include "client.h"
|
|
#include "workspace.h"
|
|
#include "layout.h"
|
|
#include "decorations.h"
|
|
#include "panel.h"
|
|
#include "keys.h"
|
|
#include "notifications.h"
|
|
#include "systray.h"
|
|
#include "news.h"
|
|
#include "applauncher.h"
|
|
#include "ai.h"
|
|
#include "util.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#include <sys/select.h>
|
|
#include <X11/Xlib.h>
|
|
#include <X11/cursorfont.h>
|
|
#include <X11/extensions/Xinerama.h>
|
|
|
|
/* Global state instance */
|
|
DWNState *dwn = NULL;
|
|
static DWNState dwn_state;
|
|
|
|
/* Signal handling */
|
|
static volatile sig_atomic_t received_signal = 0;
|
|
|
|
static void signal_handler(int sig)
|
|
{
|
|
received_signal = sig;
|
|
}
|
|
|
|
/* Crash signal handler - flush logs before dying */
|
|
static void crash_signal_handler(int sig)
|
|
{
|
|
/* Flush logs synchronously before crashing */
|
|
log_flush();
|
|
|
|
/* Re-raise the signal with default handler to get proper crash behavior */
|
|
signal(sig, SIG_DFL);
|
|
raise(sig);
|
|
}
|
|
|
|
/* X11 error handlers */
|
|
static int last_x_error = 0; /* Track last error for checking */
|
|
|
|
static int x_error_handler(Display *dpy, XErrorEvent *ev)
|
|
{
|
|
char error_text[256];
|
|
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;
|
|
|
|
/* Write all X errors to crash log for debugging */
|
|
FILE *crash = fopen("/tmp/dwn_crash.log", "a");
|
|
if (crash) {
|
|
fprintf(crash, "[X_ERROR] code=%d request=%d resource=%lu: %s\n",
|
|
ev->error_code, ev->request_code, ev->resourceid, error_text);
|
|
fflush(crash);
|
|
fsync(fileno(crash));
|
|
fclose(crash);
|
|
}
|
|
|
|
/* BadWindow errors are common and recoverable - just log and continue */
|
|
if (ev->error_code == BadWindow) {
|
|
LOG_DEBUG("X11 BadWindow error (request %d, resource %lu) - window was destroyed",
|
|
ev->request_code, ev->resourceid);
|
|
return 0; /* Continue - this is not fatal */
|
|
}
|
|
|
|
/* BadMatch, BadValue, BadDrawable are also often recoverable */
|
|
if (ev->error_code == BadMatch || ev->error_code == BadValue ||
|
|
ev->error_code == BadDrawable || ev->error_code == BadPixmap) {
|
|
LOG_WARN("X11 error: %s (request %d, resource %lu) - continuing",
|
|
error_text, ev->request_code, ev->resourceid);
|
|
return 0; /* Continue - these are recoverable */
|
|
}
|
|
|
|
/* Log other errors but don't crash */
|
|
LOG_WARN("X11 error: %s (request %d, resource %lu)",
|
|
error_text, ev->request_code, ev->resourceid);
|
|
return 0;
|
|
}
|
|
|
|
static int x_io_error_handler(Display *dpy)
|
|
{
|
|
(void)dpy;
|
|
|
|
/* Write directly to crash log - do not rely on async logging */
|
|
FILE *crash = fopen("/tmp/dwn_crash.log", "a");
|
|
if (crash) {
|
|
fprintf(crash, "[IO_ERROR] Fatal X11 I/O error - X server connection lost\n");
|
|
fflush(crash);
|
|
fsync(fileno(crash));
|
|
fclose(crash);
|
|
}
|
|
fprintf(stderr, "[IO_ERROR] Fatal X11 I/O error - X server connection lost\n");
|
|
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_ERROR("Fatal X11 I/O error - X server connection lost");
|
|
log_flush();
|
|
_exit(EXIT_FAILURE); /* Use _exit to avoid cleanup that might touch X */
|
|
return 0; /* Never reached */
|
|
}
|
|
|
|
/* Check if another WM is running */
|
|
static int wm_detected = 0;
|
|
|
|
static int wm_detect_error_handler(Display *dpy, XErrorEvent *ev)
|
|
{
|
|
(void)dpy;
|
|
(void)ev;
|
|
wm_detected = 1;
|
|
return 0;
|
|
}
|
|
|
|
static bool check_other_wm(void)
|
|
{
|
|
XSetErrorHandler(wm_detect_error_handler);
|
|
|
|
XSelectInput(dwn->display, dwn->root,
|
|
SubstructureRedirectMask | SubstructureNotifyMask);
|
|
XSync(dwn->display, False);
|
|
|
|
XSetErrorHandler(x_error_handler);
|
|
|
|
return wm_detected != 0;
|
|
}
|
|
|
|
/* Initialize multi-monitor support */
|
|
static void init_monitors(void)
|
|
{
|
|
if (!XineramaIsActive(dwn->display)) {
|
|
/* Single monitor */
|
|
dwn->monitors[0].x = 0;
|
|
dwn->monitors[0].y = 0;
|
|
dwn->monitors[0].width = dwn->screen_width;
|
|
dwn->monitors[0].height = dwn->screen_height;
|
|
dwn->monitors[0].index = 0;
|
|
dwn->monitors[0].primary = true;
|
|
dwn->monitor_count = 1;
|
|
return;
|
|
}
|
|
|
|
int num_screens;
|
|
XineramaScreenInfo *info = XineramaQueryScreens(dwn->display, &num_screens);
|
|
|
|
if (info == NULL || num_screens == 0) {
|
|
dwn->monitors[0].x = 0;
|
|
dwn->monitors[0].y = 0;
|
|
dwn->monitors[0].width = dwn->screen_width;
|
|
dwn->monitors[0].height = dwn->screen_height;
|
|
dwn->monitors[0].index = 0;
|
|
dwn->monitors[0].primary = true;
|
|
dwn->monitor_count = 1;
|
|
return;
|
|
}
|
|
|
|
dwn->monitor_count = (num_screens > MAX_MONITORS) ? MAX_MONITORS : num_screens;
|
|
|
|
for (int i = 0; i < dwn->monitor_count; i++) {
|
|
dwn->monitors[i].x = info[i].x_org;
|
|
dwn->monitors[i].y = info[i].y_org;
|
|
dwn->monitors[i].width = info[i].width;
|
|
dwn->monitors[i].height = info[i].height;
|
|
dwn->monitors[i].index = i;
|
|
dwn->monitors[i].primary = (i == 0);
|
|
}
|
|
|
|
XFree(info);
|
|
|
|
LOG_INFO("Detected %d monitor(s)", dwn->monitor_count);
|
|
}
|
|
|
|
/* Scan for existing windows */
|
|
static void scan_existing_windows(void)
|
|
{
|
|
Window root_return, parent_return;
|
|
Window *children;
|
|
unsigned int num_children;
|
|
|
|
if (XQueryTree(dwn->display, dwn->root, &root_return, &parent_return,
|
|
&children, &num_children)) {
|
|
for (unsigned int i = 0; i < num_children; i++) {
|
|
XWindowAttributes wa;
|
|
if (XGetWindowAttributes(dwn->display, children[i], &wa)) {
|
|
if (wa.map_state == IsViewable && wa.override_redirect == False) {
|
|
client_manage(children[i]);
|
|
}
|
|
}
|
|
}
|
|
if (children != NULL) {
|
|
XFree(children);
|
|
}
|
|
}
|
|
|
|
LOG_INFO("Scanned %d existing window(s)", client_count());
|
|
}
|
|
|
|
/* ========== Event handlers ========== */
|
|
|
|
static void handle_map_request(XMapRequestEvent *ev)
|
|
{
|
|
/* Defensive: validate pointers */
|
|
if (ev == NULL || dwn == NULL || dwn->display == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (ev->window == None) {
|
|
LOG_WARN("handle_map_request: received invalid window (None)");
|
|
return;
|
|
}
|
|
|
|
XWindowAttributes wa;
|
|
if (!XGetWindowAttributes(dwn->display, ev->window, &wa)) {
|
|
LOG_WARN("handle_map_request: XGetWindowAttributes failed for window %lu", ev->window);
|
|
return;
|
|
}
|
|
|
|
if (wa.override_redirect) {
|
|
return;
|
|
}
|
|
|
|
client_manage(ev->window);
|
|
}
|
|
|
|
static void handle_unmap_notify(XUnmapEvent *ev)
|
|
{
|
|
/* Defensive: validate pointers */
|
|
if (ev == NULL || dwn == NULL) {
|
|
return;
|
|
}
|
|
|
|
Client *c = client_find_by_window(ev->window);
|
|
if (c == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* Ignore synthetic events */
|
|
if (ev->send_event) {
|
|
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) {
|
|
return;
|
|
}
|
|
if (c->flags & CLIENT_MINIMIZED) {
|
|
return;
|
|
}
|
|
|
|
/* Window was actually closed/withdrawn - unmanage it */
|
|
client_unmanage(c);
|
|
}
|
|
|
|
static void handle_destroy_notify(XDestroyWindowEvent *ev)
|
|
{
|
|
/* Defensive: validate pointers */
|
|
if (ev == NULL) {
|
|
return;
|
|
}
|
|
|
|
Client *c = client_find_by_window(ev->window);
|
|
if (c != NULL) {
|
|
client_unmanage(c);
|
|
}
|
|
}
|
|
|
|
static void handle_configure_request(XConfigureRequestEvent *ev)
|
|
{
|
|
/* Defensive: validate pointers */
|
|
if (ev == NULL || dwn == NULL || dwn->display == NULL) {
|
|
return;
|
|
}
|
|
|
|
Client *c = client_find_by_window(ev->window);
|
|
|
|
if (c != NULL) {
|
|
/* Managed window - respect some requests for floating windows */
|
|
if (client_is_floating(c) || client_is_fullscreen(c)) {
|
|
if (ev->value_mask & CWX) c->x = ev->x;
|
|
if (ev->value_mask & CWY) c->y = ev->y;
|
|
if (ev->value_mask & CWWidth) c->width = ev->width;
|
|
if (ev->value_mask & CWHeight) c->height = ev->height;
|
|
client_configure(c);
|
|
} else {
|
|
/* Just send configure notify with current geometry */
|
|
client_configure(c);
|
|
}
|
|
} else {
|
|
/* Unmanaged window - pass through */
|
|
XWindowChanges wc;
|
|
wc.x = ev->x;
|
|
wc.y = ev->y;
|
|
wc.width = ev->width;
|
|
wc.height = ev->height;
|
|
wc.border_width = ev->border_width;
|
|
wc.sibling = ev->above;
|
|
wc.stack_mode = ev->detail;
|
|
|
|
XConfigureWindow(dwn->display, ev->window, ev->value_mask, &wc);
|
|
}
|
|
}
|
|
|
|
static void handle_property_notify(XPropertyEvent *ev)
|
|
{
|
|
/* Defensive: validate pointers */
|
|
if (ev == NULL || dwn == NULL) {
|
|
return;
|
|
}
|
|
|
|
Client *c = client_find_by_window(ev->window);
|
|
if (c == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (ev->atom == icccm.WM_NAME || ev->atom == ewmh.NET_WM_NAME) {
|
|
client_update_title(c);
|
|
panel_render_all();
|
|
}
|
|
}
|
|
|
|
static void handle_expose(XExposeEvent *ev)
|
|
{
|
|
/* Defensive: validate pointers */
|
|
if (ev == NULL || dwn == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* Only handle final expose in a sequence */
|
|
if (ev->count != 0) {
|
|
return;
|
|
}
|
|
|
|
/* Check if it's a panel */
|
|
if (dwn->top_panel != NULL && ev->window == dwn->top_panel->window) {
|
|
panel_render(dwn->top_panel);
|
|
return;
|
|
}
|
|
if (dwn->bottom_panel != NULL && ev->window == dwn->bottom_panel->window) {
|
|
panel_render(dwn->bottom_panel);
|
|
return;
|
|
}
|
|
|
|
/* Check if it's a notification */
|
|
Notification *notif = notification_find_by_window(ev->window);
|
|
if (notif != NULL) {
|
|
notification_render(notif);
|
|
return;
|
|
}
|
|
|
|
/* Check if it's a frame */
|
|
Client *c = client_find_by_frame(ev->window);
|
|
if (c != NULL) {
|
|
Workspace *ws = workspace_get(c->workspace);
|
|
bool focused = (ws != NULL && ws->focused == c);
|
|
decorations_render(c, focused);
|
|
}
|
|
}
|
|
|
|
static void handle_enter_notify(XCrossingEvent *ev)
|
|
{
|
|
/* Defensive: validate all pointers */
|
|
if (ev == NULL || dwn == NULL || dwn->config == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (dwn->config->focus_mode != FOCUS_FOLLOW) {
|
|
return;
|
|
}
|
|
|
|
/* Focus on enter for follow-mouse mode */
|
|
Client *c = client_find_by_frame(ev->window);
|
|
if (c == NULL) {
|
|
c = client_find_by_window(ev->window);
|
|
}
|
|
|
|
if (c != NULL && c->workspace == (unsigned int)dwn->current_workspace) {
|
|
client_focus(c);
|
|
}
|
|
}
|
|
|
|
static void handle_button_press(XButtonEvent *ev)
|
|
{
|
|
/* Defensive: validate all pointers */
|
|
if (ev == NULL || dwn == NULL || dwn->display == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* Check volume slider first */
|
|
if (volume_slider != NULL && volume_slider->visible) {
|
|
if (ev->window == volume_slider->window) {
|
|
volume_slider_handle_click(volume_slider, ev->x, ev->y);
|
|
return;
|
|
} else {
|
|
/* Clicked outside slider - close it */
|
|
volume_slider_hide(volume_slider);
|
|
}
|
|
}
|
|
|
|
/* Check WiFi dropdown menu */
|
|
if (wifi_menu != NULL && wifi_menu->visible) {
|
|
if (ev->window == wifi_menu->window) {
|
|
dropdown_handle_click(wifi_menu, ev->x, ev->y);
|
|
return;
|
|
} else {
|
|
/* Clicked outside dropdown - close it */
|
|
dropdown_hide(wifi_menu);
|
|
}
|
|
}
|
|
|
|
/* Check notifications first - clicking dismisses them */
|
|
Notification *notif = notification_find_by_window(ev->window);
|
|
if (notif != NULL) {
|
|
notification_close(notif->id);
|
|
return;
|
|
}
|
|
|
|
/* Check panels first */
|
|
if (dwn->top_panel != NULL && ev->window == dwn->top_panel->window) {
|
|
panel_handle_click(dwn->top_panel, ev->x, ev->y, ev->button);
|
|
return;
|
|
}
|
|
if (dwn->bottom_panel != NULL && ev->window == dwn->bottom_panel->window) {
|
|
panel_handle_click(dwn->bottom_panel, ev->x, ev->y, ev->button);
|
|
return;
|
|
}
|
|
|
|
/* Find client */
|
|
Client *c = client_find_by_frame(ev->window);
|
|
bool is_client_window = false;
|
|
if (c == NULL) {
|
|
c = client_find_by_window(ev->window);
|
|
is_client_window = (c != NULL);
|
|
}
|
|
|
|
if (c == NULL) {
|
|
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) {
|
|
XAllowEvents(dwn->display, ReplayPointer, ev->time);
|
|
client_focus(c);
|
|
return;
|
|
}
|
|
|
|
/* Focus on click */
|
|
client_focus(c);
|
|
|
|
/* Check for button clicks in decorations */
|
|
if (c->frame != None && ev->window == c->frame) {
|
|
ButtonType btn = decorations_hit_test_button(c, ev->x, ev->y);
|
|
if (btn != BUTTON_COUNT) {
|
|
decorations_button_press(c, btn);
|
|
return;
|
|
}
|
|
|
|
/* Check for title bar drag */
|
|
if (decorations_hit_test_title_bar(c, ev->x, ev->y)) {
|
|
if (ev->button == 1) {
|
|
/* Start move */
|
|
dwn->drag_client = c;
|
|
dwn->drag_start_x = ev->x_root;
|
|
dwn->drag_start_y = ev->y_root;
|
|
dwn->drag_orig_x = c->x;
|
|
dwn->drag_orig_y = c->y;
|
|
dwn->resizing = false;
|
|
|
|
XGrabPointer(dwn->display, c->frame, True,
|
|
PointerMotionMask | ButtonReleaseMask,
|
|
GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Check for resize */
|
|
int direction;
|
|
if (decorations_hit_test_resize_area(c, ev->x, ev->y, &direction)) {
|
|
if (ev->button == 1) {
|
|
dwn->drag_client = c;
|
|
dwn->drag_start_x = ev->x_root;
|
|
dwn->drag_start_y = ev->y_root;
|
|
dwn->drag_orig_x = c->x;
|
|
dwn->drag_orig_y = c->y;
|
|
dwn->drag_orig_w = c->width;
|
|
dwn->drag_orig_h = c->height;
|
|
dwn->resizing = true;
|
|
|
|
XGrabPointer(dwn->display, c->frame, True,
|
|
PointerMotionMask | ButtonReleaseMask,
|
|
GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void handle_button_release(XButtonEvent *ev)
|
|
{
|
|
/* Defensive: validate pointers */
|
|
if (dwn == NULL || dwn->display == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* Handle volume slider release */
|
|
if (volume_slider != NULL && volume_slider->visible && volume_slider->dragging) {
|
|
volume_slider_handle_release(volume_slider);
|
|
}
|
|
|
|
(void)ev;
|
|
|
|
if (dwn->drag_client != NULL) {
|
|
XUngrabPointer(dwn->display, CurrentTime);
|
|
dwn->drag_client = NULL;
|
|
}
|
|
}
|
|
|
|
static void handle_motion_notify(XMotionEvent *ev)
|
|
{
|
|
/* Defensive: validate pointers */
|
|
if (ev == NULL || dwn == NULL || dwn->display == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* Check volume slider drag */
|
|
if (volume_slider != NULL && volume_slider->visible && ev->window == volume_slider->window) {
|
|
volume_slider_handle_motion(volume_slider, ev->x, ev->y);
|
|
return;
|
|
}
|
|
|
|
/* Check WiFi dropdown hover */
|
|
if (wifi_menu != NULL && wifi_menu->visible && ev->window == wifi_menu->window) {
|
|
dropdown_handle_motion(wifi_menu, ev->x, ev->y);
|
|
return;
|
|
}
|
|
|
|
if (dwn->drag_client == NULL) {
|
|
return;
|
|
}
|
|
|
|
Client *c = dwn->drag_client;
|
|
if (c == NULL) {
|
|
dwn->drag_client = NULL; /* Reset invalid drag state */
|
|
return;
|
|
}
|
|
|
|
int dx = ev->x_root - dwn->drag_start_x;
|
|
int dy = ev->y_root - dwn->drag_start_y;
|
|
|
|
if (dwn->resizing) {
|
|
int new_w = dwn->drag_orig_w + dx;
|
|
int new_h = dwn->drag_orig_h + dy;
|
|
if (new_w < 50) new_w = 50;
|
|
if (new_h < 50) new_h = 50;
|
|
client_resize(c, new_w, new_h);
|
|
} else {
|
|
client_move(c, dwn->drag_orig_x + dx, dwn->drag_orig_y + dy);
|
|
}
|
|
}
|
|
|
|
static void handle_client_message(XClientMessageEvent *ev)
|
|
{
|
|
/* Defensive: validate pointers */
|
|
if (ev == NULL || dwn == NULL || dwn->display == NULL) {
|
|
return;
|
|
}
|
|
|
|
Client *c = client_find_by_window(ev->window);
|
|
|
|
if (ev->message_type == ewmh.NET_ACTIVE_WINDOW) {
|
|
if (c != NULL) {
|
|
if (c->workspace != (unsigned int)dwn->current_workspace) {
|
|
workspace_switch(c->workspace);
|
|
}
|
|
client_focus(c);
|
|
}
|
|
} else if (ev->message_type == ewmh.NET_CLOSE_WINDOW) {
|
|
if (c != NULL) {
|
|
client_close(c);
|
|
}
|
|
} else if (ev->message_type == ewmh.NET_WM_STATE) {
|
|
if (c != NULL) {
|
|
Atom action = ev->data.l[0];
|
|
Atom prop1 = ev->data.l[1];
|
|
Atom prop2 = ev->data.l[2];
|
|
|
|
bool set = (action == 1);
|
|
bool toggle = (action == 2);
|
|
|
|
if (prop1 == ewmh.NET_WM_STATE_FULLSCREEN ||
|
|
prop2 == ewmh.NET_WM_STATE_FULLSCREEN) {
|
|
if (toggle) {
|
|
client_toggle_fullscreen(c);
|
|
} else {
|
|
client_set_fullscreen(c, set);
|
|
}
|
|
}
|
|
}
|
|
} else if (ev->message_type == ewmh.NET_CURRENT_DESKTOP) {
|
|
int desktop = ev->data.l[0];
|
|
if (desktop >= 0 && desktop < MAX_WORKSPACES) {
|
|
workspace_switch(desktop);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Sync log - disabled in production (enable for debugging crashes) */
|
|
#if 0
|
|
static FILE *crash_log_file = NULL;
|
|
|
|
static void sync_log(const char *msg)
|
|
{
|
|
if (crash_log_file == NULL) {
|
|
crash_log_file = fopen("/tmp/dwn_crash.log", "a");
|
|
}
|
|
fprintf(stderr, "[SYNC] %s\n", msg);
|
|
fflush(stderr);
|
|
if (crash_log_file != NULL) {
|
|
fprintf(crash_log_file, "[SYNC] %s\n", msg);
|
|
fflush(crash_log_file);
|
|
fsync(fileno(crash_log_file));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Main event dispatcher */
|
|
void dwn_handle_event(XEvent *ev)
|
|
{
|
|
/* Defensive: validate event pointer */
|
|
if (ev == NULL) {
|
|
LOG_WARN("dwn_handle_event: received NULL event");
|
|
return;
|
|
}
|
|
|
|
/* Defensive: validate global state */
|
|
if (dwn == NULL || dwn->display == NULL) {
|
|
LOG_ERROR("dwn_handle_event: dwn or display is NULL");
|
|
return;
|
|
}
|
|
|
|
switch (ev->type) {
|
|
case MapRequest:
|
|
handle_map_request(&ev->xmaprequest);
|
|
break;
|
|
case UnmapNotify:
|
|
handle_unmap_notify(&ev->xunmap);
|
|
break;
|
|
case DestroyNotify:
|
|
handle_destroy_notify(&ev->xdestroywindow);
|
|
break;
|
|
case ConfigureRequest:
|
|
handle_configure_request(&ev->xconfigurerequest);
|
|
break;
|
|
case PropertyNotify:
|
|
handle_property_notify(&ev->xproperty);
|
|
break;
|
|
case Expose:
|
|
handle_expose(&ev->xexpose);
|
|
break;
|
|
case EnterNotify:
|
|
handle_enter_notify(&ev->xcrossing);
|
|
break;
|
|
case ButtonPress:
|
|
handle_button_press(&ev->xbutton);
|
|
break;
|
|
case ButtonRelease:
|
|
handle_button_release(&ev->xbutton);
|
|
break;
|
|
case MotionNotify:
|
|
handle_motion_notify(&ev->xmotion);
|
|
break;
|
|
case KeyPress:
|
|
keys_handle_press(&ev->xkey);
|
|
break;
|
|
case KeyRelease:
|
|
keys_handle_release(&ev->xkey);
|
|
break;
|
|
case ClientMessage:
|
|
handle_client_message(&ev->xclient);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* ========== Core functions ========== */
|
|
|
|
int dwn_init(void)
|
|
{
|
|
/* Initialize global state */
|
|
dwn = &dwn_state;
|
|
memset(dwn, 0, sizeof(DWNState));
|
|
|
|
/* Open display */
|
|
dwn->display = XOpenDisplay(NULL);
|
|
if (dwn->display == NULL) {
|
|
fprintf(stderr, "Cannot open X display\n");
|
|
return -1;
|
|
}
|
|
|
|
dwn->screen = DefaultScreen(dwn->display);
|
|
dwn->root = RootWindow(dwn->display, dwn->screen);
|
|
dwn->screen_width = DisplayWidth(dwn->display, dwn->screen);
|
|
dwn->screen_height = DisplayHeight(dwn->display, dwn->screen);
|
|
dwn->colormap = DefaultColormap(dwn->display, dwn->screen);
|
|
|
|
/* Set error handlers */
|
|
XSetErrorHandler(x_error_handler);
|
|
XSetIOErrorHandler(x_io_error_handler);
|
|
|
|
/* Check for other WM */
|
|
if (check_other_wm()) {
|
|
fprintf(stderr, "Another window manager is already running\n");
|
|
XCloseDisplay(dwn->display);
|
|
return -1;
|
|
}
|
|
|
|
/* Initialize logging */
|
|
log_init("~/.local/share/dwn/dwn.log");
|
|
LOG_INFO("DWN %s starting", DWN_VERSION);
|
|
|
|
/* Load configuration */
|
|
dwn->config = config_create();
|
|
config_load(dwn->config, NULL);
|
|
|
|
/* Initialize colors (after display is open) */
|
|
extern void config_init_colors(Config *cfg);
|
|
config_init_colors(dwn->config);
|
|
|
|
/* Load font */
|
|
dwn->font = XLoadQueryFont(dwn->display, dwn->config->font_name);
|
|
if (dwn->font == NULL) {
|
|
dwn->font = XLoadQueryFont(dwn->display, "fixed");
|
|
}
|
|
|
|
/* Load Xft font for UTF-8 support */
|
|
dwn->xft_font = XftFontOpenName(dwn->display, dwn->screen,
|
|
"monospace:size=10:antialias=true");
|
|
if (dwn->xft_font == NULL) {
|
|
dwn->xft_font = XftFontOpenName(dwn->display, dwn->screen,
|
|
"fixed:size=10");
|
|
}
|
|
if (dwn->xft_font != NULL) {
|
|
LOG_INFO("Loaded Xft font for UTF-8 support");
|
|
}
|
|
|
|
/* Create GC */
|
|
XGCValues gcv;
|
|
gcv.foreground = dwn->config->colors.panel_fg;
|
|
gcv.background = dwn->config->colors.panel_bg;
|
|
gcv.font = dwn->font ? dwn->font->fid : None;
|
|
dwn->gc = XCreateGC(dwn->display, dwn->root,
|
|
GCForeground | GCBackground | (dwn->font ? GCFont : 0), &gcv);
|
|
|
|
/* Initialize atoms */
|
|
atoms_init(dwn->display);
|
|
|
|
/* Initialize monitors */
|
|
init_monitors();
|
|
|
|
/* Initialize workspaces */
|
|
workspace_init();
|
|
|
|
/* Initialize decorations */
|
|
decorations_init();
|
|
|
|
/* Initialize panels */
|
|
panels_init();
|
|
|
|
/* Initialize system tray (WiFi, Audio indicators) */
|
|
systray_init();
|
|
|
|
/* Initialize news ticker */
|
|
news_init();
|
|
|
|
/* Initialize app launcher */
|
|
applauncher_init();
|
|
|
|
/* Initialize keyboard shortcuts */
|
|
keys_init();
|
|
|
|
/* Initialize D-Bus notifications */
|
|
notifications_init();
|
|
|
|
/* Initialize AI */
|
|
ai_init();
|
|
|
|
/* Setup EWMH */
|
|
atoms_setup_ewmh();
|
|
|
|
/* Select events on root window */
|
|
XSelectInput(dwn->display, dwn->root,
|
|
SubstructureRedirectMask | SubstructureNotifyMask |
|
|
StructureNotifyMask | PropertyChangeMask |
|
|
ButtonPressMask | PointerMotionMask);
|
|
|
|
/* Set cursor */
|
|
Cursor cursor = XCreateFontCursor(dwn->display, XC_left_ptr);
|
|
XDefineCursor(dwn->display, dwn->root, cursor);
|
|
|
|
/* Scan existing windows */
|
|
scan_existing_windows();
|
|
|
|
/* Arrange initial workspace */
|
|
workspace_arrange_current();
|
|
|
|
/* Render panels */
|
|
panel_render_all();
|
|
|
|
dwn->running = true;
|
|
|
|
LOG_INFO("DWN initialized successfully");
|
|
|
|
return 0;
|
|
}
|
|
|
|
void dwn_cleanup(void)
|
|
{
|
|
LOG_INFO("DWN shutting down");
|
|
|
|
/* Cleanup subsystems */
|
|
ai_cleanup();
|
|
notifications_cleanup();
|
|
news_cleanup();
|
|
applauncher_cleanup();
|
|
keys_cleanup();
|
|
systray_cleanup();
|
|
panels_cleanup();
|
|
decorations_cleanup();
|
|
workspace_cleanup();
|
|
|
|
/* Unmanage all clients */
|
|
while (dwn->client_list != NULL) {
|
|
client_unmanage(dwn->client_list);
|
|
}
|
|
|
|
/* Free resources */
|
|
if (dwn->gc != None) {
|
|
XFreeGC(dwn->display, dwn->gc);
|
|
}
|
|
if (dwn->font != NULL) {
|
|
XFreeFont(dwn->display, dwn->font);
|
|
}
|
|
if (dwn->xft_font != NULL) {
|
|
XftFontClose(dwn->display, dwn->xft_font);
|
|
}
|
|
if (dwn->config != NULL) {
|
|
config_destroy(dwn->config);
|
|
}
|
|
|
|
/* Close display */
|
|
if (dwn->display != NULL) {
|
|
XCloseDisplay(dwn->display);
|
|
}
|
|
|
|
log_close();
|
|
|
|
dwn = NULL;
|
|
}
|
|
|
|
void dwn_run(void)
|
|
{
|
|
int x11_fd = ConnectionNumber(dwn->display);
|
|
int dbus_fd = -1;
|
|
|
|
if (dbus_conn != NULL) {
|
|
dbus_connection_get_unix_fd(dbus_conn, &dbus_fd);
|
|
}
|
|
|
|
long last_clock_update = 0;
|
|
long last_news_update = 0;
|
|
|
|
while (dwn->running && received_signal == 0) {
|
|
/* Handle pending X events */
|
|
while (XPending(dwn->display)) {
|
|
XEvent ev;
|
|
XNextEvent(dwn->display, &ev);
|
|
dwn_handle_event(&ev);
|
|
}
|
|
|
|
/* Process D-Bus messages */
|
|
notifications_process_messages();
|
|
|
|
/* Process AI requests */
|
|
ai_process_pending();
|
|
|
|
/* Process Exa requests */
|
|
exa_process_pending();
|
|
|
|
/* Update notifications (check for expired) */
|
|
notifications_update();
|
|
|
|
long now = get_time_ms();
|
|
|
|
/* Update news ticker frequently for smooth scrolling (~60fps) */
|
|
if (now - last_news_update >= 16) {
|
|
news_update();
|
|
panel_render_all();
|
|
last_news_update = now;
|
|
}
|
|
|
|
/* Update clock and system stats every second */
|
|
if (now - last_clock_update >= 1000) {
|
|
panel_update_clock();
|
|
panel_update_system_stats();
|
|
systray_update();
|
|
last_clock_update = now;
|
|
}
|
|
|
|
/* Wait for events with timeout - short for smooth animation */
|
|
fd_set fds;
|
|
FD_ZERO(&fds);
|
|
FD_SET(x11_fd, &fds);
|
|
if (dbus_fd >= 0) {
|
|
FD_SET(dbus_fd, &fds);
|
|
}
|
|
|
|
struct timeval tv;
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 16000; /* ~16ms for 60fps smooth scrolling */
|
|
|
|
int max_fd = x11_fd;
|
|
if (dbus_fd > max_fd) max_fd = dbus_fd;
|
|
|
|
select(max_fd + 1, &fds, NULL, NULL, &tv);
|
|
}
|
|
|
|
/* Handle signal */
|
|
if (received_signal != 0) {
|
|
LOG_INFO("Received signal %d", received_signal);
|
|
}
|
|
}
|
|
|
|
void dwn_quit(void)
|
|
{
|
|
dwn->running = false;
|
|
}
|
|
|
|
/* ========== Main ========== */
|
|
|
|
static void print_usage(const char *program)
|
|
{
|
|
printf("Usage: %s [OPTIONS]\n", program);
|
|
printf("\n");
|
|
printf("Options:\n");
|
|
printf(" -h, --help Show this help message\n");
|
|
printf(" -v, --version Show version information\n");
|
|
printf(" -c CONFIG Use specified config file\n");
|
|
printf("\n");
|
|
printf("Environment variables:\n");
|
|
printf(" OPENROUTER_API_KEY Enable AI features\n");
|
|
printf(" EXA_API_KEY Enable semantic search\n");
|
|
}
|
|
|
|
static void print_version(void)
|
|
{
|
|
printf("DWN - Desktop Window Manager %s\n", DWN_VERSION);
|
|
printf("AI-enhanced X11 window manager\n");
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
/* Parse arguments */
|
|
for (int i = 1; i < argc; i++) {
|
|
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
|
print_usage(argv[0]);
|
|
return 0;
|
|
}
|
|
if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0) {
|
|
print_version();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Setup signal handlers */
|
|
signal(SIGTERM, signal_handler);
|
|
signal(SIGINT, signal_handler);
|
|
signal(SIGHUP, signal_handler);
|
|
|
|
/* Setup crash signal handlers to flush logs before dying */
|
|
signal(SIGSEGV, crash_signal_handler);
|
|
signal(SIGABRT, crash_signal_handler);
|
|
signal(SIGFPE, crash_signal_handler);
|
|
signal(SIGBUS, crash_signal_handler);
|
|
signal(SIGILL, crash_signal_handler);
|
|
|
|
/* Initialize */
|
|
if (dwn_init() != 0) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* Run event loop */
|
|
dwn_run();
|
|
|
|
/* Cleanup */
|
|
dwn_cleanup();
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|