Initial commit: SkyOS source code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-07-23 12:14:34 -04:00
commit e641720a3c
30 files changed
+5836

No files matched your search

+30
View File
@@ -0,0 +1,30 @@
CC = gcc
# pkg-config gives correct flags on both Linux and macOS/Homebrew.
# Fallback covers systems where pkg-config isn't available.
PKG_XFT_C := $(shell pkg-config --cflags xft x11 2>/dev/null)
PKG_XFT_L := $(shell pkg-config --libs xft x11 2>/dev/null)
ifeq ($(PKG_XFT_C),)
# macOS Homebrew fallback
PKG_XFT_C := -I/opt/homebrew/include
PKG_XFT_L := -L/opt/homebrew/lib -lXft -lfontconfig
endif
CFLAGS = -Wall -Wextra -O2 -Iinclude $(PKG_XFT_C)
LDFLAGS = $(PKG_XFT_L)
SRC = src/wm.c src/client.c src/events.c src/draw.c src/color.c src/menu.c
OBJ = $(SRC:.c=.o)
BIN = skywm
.PHONY: all clean install
all: $(BIN)
$(BIN): $(OBJ)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
install: $(BIN)
install -Dm755 $(BIN) /usr/local/bin/$(BIN)
clean:
rm -f $(OBJ) $(BIN)
+180
View File
@@ -0,0 +1,180 @@
#ifndef WM_H
#define WM_H
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>
#include <X11/Xft/Xft.h>
#include <X11/cursorfont.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/* ── palette (modern dark) ───────────────────────────────────────────── */
#define COLOR_TITLE_ACTIVE 0x1E4D78 /* deep blue active title bar */
#define COLOR_TITLE_INACTIVE 0x252830 /* dark grey inactive */
#define COLOR_TITLE_TEXT_ACT 0xE8EEF8 /* near-white active text */
#define COLOR_TITLE_TEXT_INACT 0x5C6070 /* dimmed inactive text */
#define COLOR_BORDER_LIGHT 0x3A6898 /* blue-grey highlight */
#define COLOR_BORDER_DARK 0x0C1018 /* near-black shadow */
#define COLOR_CLOSE_BTN 0xCC3333 /* red close */
#define COLOR_MAXIMIZE_BTN 0x2E8B44 /* green maximize */
#define COLOR_MINIMIZE_BTN 0xBB8820 /* amber minimize */
#define COLOR_CLOSE_BTN_HOV 0xFF5050
#define COLOR_MAXIMIZE_BTN_HOV 0x44CC66
#define COLOR_MINIMIZE_BTN_HOV 0xFFBB30
#define COLOR_BG 0x1A2840 /* desktop background */
#define COLOR_SNAP_OVERLAY 0x3A6898 /* snap highlight (unused, placeholder) */
/* ── geometry ────────────────────────────────────────────────────────── */
#define TITLE_HEIGHT 28 /* taller for modern look */
#define TITLE_TAB_W 200
#define BORDER_W 3
#define BTN_SIZE 14
#define BTN_MARGIN 5
#define RESIZE_GRIP 12
#define MIN_WIN_W 120
#define MIN_WIN_H 60
#define SNAP_DIST 24 /* px from edge to trigger snap */
#define DBL_CLICK_MS 400 /* double-click threshold */
/*
* Three buttons on right side (left to right): minimize, maximize, close
* close = fw - BTN_SIZE - BTN_MARGIN - BORDER_W
* max = close - BTN_SIZE - BTN_MARGIN - 2
* min = max - BTN_SIZE - BTN_MARGIN - 2
*/
#define BTN_CLOSE_X(fw) ((fw) - BTN_SIZE - BTN_MARGIN - BORDER_W)
#define BTN_MAX_X(fw) ((fw) - BTN_SIZE*2 - BTN_MARGIN*2 - BORDER_W - 2)
#define BTN_MIN_X(fw) ((fw) - BTN_SIZE*3 - BTN_MARGIN*3 - BORDER_W - 4)
#define BTN_Y (((TITLE_HEIGHT) - BTN_SIZE) / 2)
/* ── fonts ───────────────────────────────────────────────────────────── */
#define FONT_TITLE "Sans:size=9:weight=bold"
#define FONT_MENU "Sans:size=10"
/* ── drag mode ───────────────────────────────────────────────────────── */
typedef enum { DRAG_NONE, DRAG_MOVE, DRAG_RESIZE } DragMode;
/* WM_STATE values (ICCCM) */
#define WM_STATE_NORMAL 1
#define WM_STATE_ICONIC 3
#define WM_STATE_WITHDRAWN 0
typedef struct Client {
Window win;
Window frame;
int x, y;
unsigned int w, h;
int focused;
int minimized;
int maximized;
int saved_x, saved_y; /* pre-maximize position */
unsigned saved_w, saved_h; /* pre-maximize size */
Time last_click_time; /* for double-click detection */
char title[256];
struct Client *next;
} Client;
typedef struct MenuItem {
const char *label;
const char *cmd;
} MenuItem;
typedef struct WM {
Display *dpy;
int screen;
Window root;
GC gc;
Colormap cmap;
Visual *visual;
int depth;
XftFont *font_title;
XftFont *font_menu;
Cursor cur_normal;
Cursor cur_move;
Cursor cur_resize;
Client *clients;
Client *focused;
Atom wm_delete_window;
Atom wm_protocols;
Atom wm_state;
Atom net_wm_name;
Atom net_wm_state;
Atom net_wm_state_hidden;
Atom net_wm_state_maximized_vert;
Atom net_wm_state_maximized_horz;
DragMode drag_mode;
int drag_start_x, drag_start_y;
int drag_win_x, drag_win_y;
unsigned drag_win_w, drag_win_h;
Window drag_frame;
Window menu_win;
int menu_visible;
int menu_x, menu_y;
int menu_hovered;
} WM;
/* core */
void wm_init(WM *wm);
void wm_run(WM *wm);
void wm_cleanup(WM *wm);
/* client */
Client *client_add(WM *wm, Window win);
void client_remove(WM *wm, Window win);
Client *client_find_by_frame(WM *wm, Window frame);
Client *client_find_by_win(WM *wm, Window win);
void client_focus(WM *wm, Client *c);
void client_raise(WM *wm, Client *c);
void client_minimize(WM *wm, Client *c);
void client_restore(WM *wm, Client *c);
void client_maximize(WM *wm, Client *c);
void client_unmaximize(WM *wm, Client *c);
void client_toggle_maximize(WM *wm, Client *c);
void client_snap_left(WM *wm, Client *c);
void client_snap_right(WM *wm, Client *c);
void client_set_wm_state(WM *wm, Client *c, long state);
/* drawing */
void frame_draw(WM *wm, Client *c);
void frame_draw_buttons(WM *wm, Client *c);
unsigned long color_alloc(WM *wm, unsigned long rgb);
void xft_color(WM *wm, unsigned long rgb, XftColor *out);
/* hit testing */
int frame_hit_resize(Client *c, int x, int y);
int frame_hit_close(Client *c, int x, int y);
int frame_hit_minimize(Client *c, int x, int y);
int frame_hit_maximize(Client *c, int x, int y);
/* menu */
void menu_show(WM *wm, int x, int y);
void menu_hide(WM *wm);
void menu_draw(WM *wm);
void menu_click(WM *wm, int px, int py);
int menu_item_at(int py);
/* events */
void on_map_request(WM *wm, XMapRequestEvent *e);
void on_unmap_notify(WM *wm, XUnmapEvent *e);
void on_destroy_notify(WM *wm, XDestroyWindowEvent *e);
void on_expose(WM *wm, XExposeEvent *e);
void on_button_press(WM *wm, XButtonEvent *e);
void on_button_release(WM *wm, XButtonEvent *e);
void on_motion_notify(WM *wm, XMotionEvent *e);
void on_configure_request(WM *wm, XConfigureRequestEvent *e);
void on_key_press(WM *wm, XKeyEvent *e);
void on_enter_notify(WM *wm, XCrossingEvent *e);
void on_client_message(WM *wm, XClientMessageEvent *e);
void on_property_notify(WM *wm, XPropertyEvent *e);
#endif
+315
View File
@@ -0,0 +1,315 @@
#include "wm.h"
/* ── WM_STATE ICCCM helper ───────────────────────────────────────────── */
void client_set_wm_state(WM *wm, Client *c, long state) {
long data[2] = { state, None };
XChangeProperty(wm->dpy, c->win, wm->wm_state, wm->wm_state,
32, PropModeReplace, (unsigned char *)data, 2);
if (state == WM_STATE_ICONIC) {
XChangeProperty(wm->dpy, c->win, wm->net_wm_state, XA_ATOM,
32, PropModeReplace,
(unsigned char *)&wm->net_wm_state_hidden, 1);
} else {
XDeleteProperty(wm->dpy, c->win, wm->net_wm_state);
}
}
static void update_net_client_list(WM *wm) {
Atom net_client_list = XInternAtom(wm->dpy, "_NET_CLIENT_LIST", False);
int count = 0;
for (Client *c = wm->clients; c; c = c->next) count++;
Window *wins = calloc(count, sizeof(Window));
int i = 0;
for (Client *c = wm->clients; c; c = c->next) wins[i++] = c->win;
XChangeProperty(wm->dpy, wm->root, net_client_list, XA_WINDOW, 32,
PropModeReplace, (unsigned char *)wins, count);
free(wins);
}
/* ── add / remove ────────────────────────────────────────────────────── */
/* smart placement: cascade from top-left, offset each window */
static void smart_place(WM *wm, Client *c) {
static int cascade_x = 40, cascade_y = 40;
int sw = DisplayWidth(wm->dpy, wm->screen);
int sh = DisplayHeight(wm->dpy, wm->screen);
/* reset cascade if it would go off-screen */
if (cascade_x + (int)c->w > sw - 40 || cascade_y + (int)c->h > sh - 40) {
cascade_x = 40;
cascade_y = 40;
}
c->x = cascade_x;
c->y = cascade_y;
cascade_x += 30;
cascade_y += 30;
}
Client *client_add(WM *wm, Window win) {
XWindowAttributes wa;
if (!XGetWindowAttributes(wm->dpy, win, &wa)) return NULL;
if (wa.override_redirect) return NULL;
if (client_find_by_win(wm, win)) return NULL;
Client *c = calloc(1, sizeof(Client));
c->win = win;
c->w = wa.width;
c->h = wa.height;
/* use application-requested position if non-zero, otherwise smart place */
if (wa.x == 0 && wa.y == 0) {
smart_place(wm, c);
} else {
c->x = wa.x;
c->y = wa.y;
}
/* never land behind deskbar (top-right panel ~180px wide) */
int sw = DisplayWidth(wm->dpy, wm->screen);
if (c->x + (int)c->w > sw - 185 && c->y < 200)
c->y = 40;
/* fetch title */
char *name = NULL;
Atom utf8 = XInternAtom(wm->dpy, "UTF8_STRING", False);
Atom net_wm = XInternAtom(wm->dpy, "_NET_WM_NAME", False);
{
Atom type; int fmt; unsigned long n, after;
unsigned char *data = NULL;
if (XGetWindowProperty(wm->dpy, win, net_wm, 0, 256, False,
utf8, &type, &fmt, &n, &after, &data)
== Success && data && n > 0) {
snprintf(c->title, 255, "%.*s", (int)n, data);
XFree(data);
} else {
if (data) XFree(data);
if (XFetchName(wm->dpy, win, &name) && name) {
strncpy(c->title, name, 255);
XFree(name);
} else {
strncpy(c->title, "Untitled", 255);
}
}
}
int fw = (int)c->w + BORDER_W * 2;
int fh = (int)c->h + TITLE_HEIGHT + BORDER_W;
XSetWindowAttributes swa;
swa.background_pixel = color_alloc(wm, COLOR_TITLE_INACTIVE);
swa.event_mask = SubstructureRedirectMask | SubstructureNotifyMask
| ButtonPressMask | ButtonReleaseMask
| PointerMotionMask | ExposureMask
| EnterWindowMask;
swa.override_redirect = False;
swa.cursor = wm->cur_normal;
c->frame = XCreateWindow(
wm->dpy, wm->root,
c->x, c->y, fw, fh, 0,
CopyFromParent, InputOutput, CopyFromParent,
CWBackPixel | CWEventMask | CWOverrideRedirect | CWCursor,
&swa
);
XReparentWindow(wm->dpy, win, c->frame, BORDER_W, TITLE_HEIGHT);
XSelectInput(wm->dpy, win, PropertyChangeMask | FocusChangeMask);
c->next = wm->clients;
wm->clients = c;
XMapWindow(wm->dpy, c->frame);
XMapWindow(wm->dpy, win);
client_set_wm_state(wm, c, WM_STATE_NORMAL);
update_net_client_list(wm);
client_focus(wm, c);
return c;
}
void client_remove(WM *wm, Window win) {
Client *prev = NULL, *c = wm->clients;
while (c) {
if (c->win == win || c->frame == win) {
if (prev) prev->next = c->next;
else wm->clients = c->next;
if (wm->focused == c) {
wm->focused = wm->clients;
if (wm->focused) client_focus(wm, wm->focused);
else wm->focused = NULL;
}
XDestroyWindow(wm->dpy, c->frame);
free(c);
update_net_client_list(wm);
return;
}
prev = c; c = c->next;
}
}
/* ── lookup ──────────────────────────────────────────────────────────── */
Client *client_find_by_frame(WM *wm, Window frame) {
for (Client *c = wm->clients; c; c = c->next)
if (c->frame == frame) return c;
return NULL;
}
Client *client_find_by_win(WM *wm, Window win) {
for (Client *c = wm->clients; c; c = c->next)
if (c->win == win) return c;
return NULL;
}
/* ── focus / raise ───────────────────────────────────────────────────── */
void client_focus(WM *wm, Client *c) {
if (!c || c->minimized) return;
if (wm->focused && wm->focused != c) {
wm->focused->focused = 0;
frame_draw(wm, wm->focused);
}
wm->focused = c;
c->focused = 1;
XSetInputFocus(wm->dpy, c->win, RevertToPointerRoot, CurrentTime);
XRaiseWindow(wm->dpy, c->frame);
frame_draw(wm, c);
/* advertise active window */
Atom net_active = XInternAtom(wm->dpy, "_NET_ACTIVE_WINDOW", False);
XChangeProperty(wm->dpy, wm->root, net_active, XA_WINDOW, 32,
PropModeReplace, (unsigned char *)&c->win, 1);
}
void client_raise(WM *wm, Client *c) {
XRaiseWindow(wm->dpy, c->frame);
}
/* ── minimize / restore ──────────────────────────────────────────────── */
void client_minimize(WM *wm, Client *c) {
if (c->minimized) return;
if (c->maximized) client_unmaximize(wm, c);
c->minimized = 1;
c->focused = 0;
XUnmapWindow(wm->dpy, c->frame);
client_set_wm_state(wm, c, WM_STATE_ICONIC);
if (wm->focused == c) {
wm->focused = NULL;
for (Client *n = wm->clients; n; n = n->next) {
if (!n->minimized) { client_focus(wm, n); break; }
}
}
}
void client_restore(WM *wm, Client *c) {
if (!c->minimized) { client_focus(wm, c); return; }
c->minimized = 0;
XMapWindow(wm->dpy, c->frame);
client_set_wm_state(wm, c, WM_STATE_NORMAL);
client_focus(wm, c);
}
/* ── maximize / unmaximize ───────────────────────────────────────────── */
void client_maximize(WM *wm, Client *c) {
if (c->maximized || c->minimized) return;
/* save current geometry */
c->saved_x = c->x;
c->saved_y = c->y;
c->saved_w = c->w;
c->saved_h = c->h;
c->maximized = 1;
int sw = DisplayWidth(wm->dpy, wm->screen);
int sh = DisplayHeight(wm->dpy, wm->screen);
/* leave room for deskbar on the right */
int deskbar_w = 185;
int new_w = sw - deskbar_w - BORDER_W * 2;
int new_h = sh - TITLE_HEIGHT - BORDER_W;
c->x = 0; c->y = 0;
c->w = (unsigned)new_w;
c->h = (unsigned)new_h;
int fw = (int)c->w + BORDER_W * 2;
int fh = (int)c->h + TITLE_HEIGHT + BORDER_W;
XMoveResizeWindow(wm->dpy, c->frame, c->x, c->y, fw, fh);
XResizeWindow(wm->dpy, c->win, c->w, c->h);
/* advertise maximized state via EWMH */
Atom states[2] = { wm->net_wm_state_maximized_vert,
wm->net_wm_state_maximized_horz };
XChangeProperty(wm->dpy, c->win, wm->net_wm_state, XA_ATOM, 32,
PropModeReplace, (unsigned char *)states, 2);
client_focus(wm, c);
frame_draw(wm, c);
}
void client_unmaximize(WM *wm, Client *c) {
if (!c->maximized) return;
c->maximized = 0;
c->x = c->saved_x;
c->y = c->saved_y;
c->w = c->saved_w;
c->h = c->saved_h;
int fw = (int)c->w + BORDER_W * 2;
int fh = (int)c->h + TITLE_HEIGHT + BORDER_W;
XMoveResizeWindow(wm->dpy, c->frame, c->x, c->y, fw, fh);
XResizeWindow(wm->dpy, c->win, c->w, c->h);
XDeleteProperty(wm->dpy, c->win, wm->net_wm_state);
client_focus(wm, c);
frame_draw(wm, c);
}
void client_toggle_maximize(WM *wm, Client *c) {
if (c->maximized) client_unmaximize(wm, c);
else client_maximize(wm, c);
}
/* ── edge snap ───────────────────────────────────────────────────────── */
void client_snap_left(WM *wm, Client *c) {
if (c->maximized) client_unmaximize(wm, c);
int sw = DisplayWidth(wm->dpy, wm->screen);
int sh = DisplayHeight(wm->dpy, wm->screen);
int deskbar_w = 185;
int half_w = (sw - deskbar_w) / 2;
c->x = 0;
c->y = 0;
c->w = (unsigned)(half_w - BORDER_W * 2);
c->h = (unsigned)(sh - TITLE_HEIGHT - BORDER_W);
int fw = (int)c->w + BORDER_W * 2;
int fh = (int)c->h + TITLE_HEIGHT + BORDER_W;
XMoveResizeWindow(wm->dpy, c->frame, c->x, c->y, fw, fh);
XResizeWindow(wm->dpy, c->win, c->w, c->h);
frame_draw(wm, c);
}
void client_snap_right(WM *wm, Client *c) {
if (c->maximized) client_unmaximize(wm, c);
int sw = DisplayWidth(wm->dpy, wm->screen);
int sh = DisplayHeight(wm->dpy, wm->screen);
int deskbar_w = 185;
int half_w = (sw - deskbar_w) / 2;
c->x = half_w;
c->y = 0;
c->w = (unsigned)(sw - deskbar_w - half_w - BORDER_W * 2);
c->h = (unsigned)(sh - TITLE_HEIGHT - BORDER_W);
int fw = (int)c->w + BORDER_W * 2;
int fh = (int)c->h + TITLE_HEIGHT + BORDER_W;
XMoveResizeWindow(wm->dpy, c->frame, c->x, c->y, fw, fh);
XResizeWindow(wm->dpy, c->win, c->w, c->h);
frame_draw(wm, c);
}
+20
View File
@@ -0,0 +1,20 @@
#include "wm.h"
unsigned long color_alloc(WM *wm, unsigned long rgb) {
XColor c;
c.red = ((rgb >> 16) & 0xFF) * 257;
c.green = ((rgb >> 8) & 0xFF) * 257;
c.blue = ((rgb) & 0xFF) * 257;
c.flags = DoRed | DoGreen | DoBlue;
XAllocColor(wm->dpy, wm->cmap, &c);
return c.pixel;
}
void xft_color(WM *wm, unsigned long rgb, XftColor *out) {
XRenderColor rc;
rc.red = ((rgb >> 16) & 0xFF) * 257;
rc.green = ((rgb >> 8) & 0xFF) * 257;
rc.blue = ((rgb) & 0xFF) * 257;
rc.alpha = 0xFFFF;
XftColorAllocValue(wm->dpy, wm->visual, wm->cmap, &rc, out);
}
+162
View File
@@ -0,0 +1,162 @@
#include "wm.h"
/*
* Frame layout (3 buttons on right):
*
* ┌─[Title______________]──────[_][□][X]─┐ <- TITLE_HEIGHT
* │ content │
* │ [grip]────┘
*/
/* ── hit tests ───────────────────────────────────────────────────────── */
int frame_hit_close(Client *c, int x, int y) {
int fw = (int)c->w + BORDER_W * 2;
int bx = BTN_CLOSE_X(fw);
return (x >= bx && x <= bx + BTN_SIZE &&
y >= BTN_Y && y <= BTN_Y + BTN_SIZE);
}
int frame_hit_maximize(Client *c, int x, int y) {
int fw = (int)c->w + BORDER_W * 2;
int bx = BTN_MAX_X(fw);
return (x >= bx && x <= bx + BTN_SIZE &&
y >= BTN_Y && y <= BTN_Y + BTN_SIZE);
}
int frame_hit_minimize(Client *c, int x, int y) {
int fw = (int)c->w + BORDER_W * 2;
int bx = BTN_MIN_X(fw);
return (x >= bx && x <= bx + BTN_SIZE &&
y >= BTN_Y && y <= BTN_Y + BTN_SIZE);
}
int frame_hit_resize(Client *c, int x, int y) {
int fw = (int)c->w + BORDER_W * 2;
int fh = (int)c->h + TITLE_HEIGHT + BORDER_W;
return (x >= fw - RESIZE_GRIP && y >= fh - RESIZE_GRIP);
}
/* ── button drawing ──────────────────────────────────────────────────── */
static void draw_circle_btn(WM *wm, Drawable d, int bx, int by,
unsigned long face, char sym) {
int r = BTN_SIZE / 2;
int cx = bx + r;
int cy = by + r;
/* filled circle */
XSetForeground(wm->dpy, wm->gc, face);
XFillArc(wm->dpy, d, wm->gc, bx, by, BTN_SIZE, BTN_SIZE, 0, 360*64);
/* subtle dark ring */
unsigned long darker = (face > 0x303030) ? face - 0x202020 : 0;
XSetForeground(wm->dpy, wm->gc, darker);
XDrawArc(wm->dpy, d, wm->gc, bx, by, BTN_SIZE, BTN_SIZE, 0, 360*64);
/* symbol in white */
XSetForeground(wm->dpy, wm->gc, 0xFFFFFF);
if (sym == 'X') {
XDrawLine(wm->dpy, d, wm->gc, cx-3, cy-3, cx+3, cy+3);
XDrawLine(wm->dpy, d, wm->gc, cx+3, cy-3, cx-3, cy+3);
XDrawLine(wm->dpy, d, wm->gc, cx-3, cy-3, cx+3, cy+3); /* double for boldness */
} else if (sym == '_') {
XDrawLine(wm->dpy, d, wm->gc, cx-3, cy+2, cx+3, cy+2);
XDrawLine(wm->dpy, d, wm->gc, cx-3, cy+3, cx+3, cy+3);
} else if (sym == 'O') {
/* maximize: small hollow square */
XDrawRectangle(wm->dpy, d, wm->gc, cx-3, cy-3, 6, 6);
}
}
void frame_draw_buttons(WM *wm, Client *c) {
int fw = (int)c->w + BORDER_W * 2;
draw_circle_btn(wm, c->frame, BTN_CLOSE_X(fw), BTN_Y,
color_alloc(wm, COLOR_CLOSE_BTN), 'X');
draw_circle_btn(wm, c->frame, BTN_MAX_X(fw), BTN_Y,
color_alloc(wm, COLOR_MAXIMIZE_BTN), 'O');
draw_circle_btn(wm, c->frame, BTN_MIN_X(fw), BTN_Y,
color_alloc(wm, COLOR_MINIMIZE_BTN), '_');
}
/* ── resize grip ─────────────────────────────────────────────────────── */
static void draw_resize_grip(WM *wm, Client *c) {
if (c->maximized) return; /* no resize grip when maximized */
int fw = (int)c->w + BORDER_W * 2;
int fh = (int)c->h + TITLE_HEIGHT + BORDER_W;
for (int i = 0; i < 4; i++) {
int ox = fw - 4 - i * 3;
int oy = fh - 4 - i * 3;
XSetForeground(wm->dpy, wm->gc, color_alloc(wm, COLOR_BORDER_LIGHT));
XDrawPoint(wm->dpy, c->frame, wm->gc, ox, oy);
XSetForeground(wm->dpy, wm->gc, color_alloc(wm, COLOR_BORDER_DARK));
XDrawPoint(wm->dpy, c->frame, wm->gc, ox+1, oy+1);
}
}
/* ── main frame draw ─────────────────────────────────────────────────── */
void frame_draw(WM *wm, Client *c) {
int fw = (int)c->w + BORDER_W * 2;
int fh = (int)c->h + TITLE_HEIGHT + BORDER_W;
unsigned long col_light = color_alloc(wm, COLOR_BORDER_LIGHT);
unsigned long col_dark = color_alloc(wm, COLOR_BORDER_DARK);
/* title bar background */
unsigned long col_title = color_alloc(wm,
c->focused ? COLOR_TITLE_ACTIVE : COLOR_TITLE_INACTIVE);
XSetForeground(wm->dpy, wm->gc, col_title);
XFillRectangle(wm->dpy, c->frame, wm->gc, 0, 0, fw, TITLE_HEIGHT);
/* subtle gradient effect: lighter top strip (2px) */
if (c->focused) {
unsigned long lighter = color_alloc(wm, 0x2A6098);
XSetForeground(wm->dpy, wm->gc, lighter);
XFillRectangle(wm->dpy, c->frame, wm->gc, 0, 0, fw, 2);
}
/* client area background strip (below title) */
XSetForeground(wm->dpy, wm->gc, col_dark);
XFillRectangle(wm->dpy, c->frame, wm->gc, 0, TITLE_HEIGHT, BORDER_W, fh - TITLE_HEIGHT);
XFillRectangle(wm->dpy, c->frame, wm->gc, fw - BORDER_W, TITLE_HEIGHT, BORDER_W, fh - TITLE_HEIGHT);
XFillRectangle(wm->dpy, c->frame, wm->gc, 0, fh - BORDER_W, fw, BORDER_W);
/* outer bevel */
XSetForeground(wm->dpy, wm->gc, col_light);
XDrawLine(wm->dpy, c->frame, wm->gc, 0, 0, fw - 1, 0);
XDrawLine(wm->dpy, c->frame, wm->gc, 0, 0, 0, fh - 1);
XSetForeground(wm->dpy, wm->gc, col_dark);
XDrawLine(wm->dpy, c->frame, wm->gc, fw-1, 0, fw-1, fh-1);
XDrawLine(wm->dpy, c->frame, wm->gc, 0, fh-1, fw-1, fh-1);
/* thin separator line under title */
XSetForeground(wm->dpy, wm->gc, col_dark);
XDrawLine(wm->dpy, c->frame, wm->gc, 0, TITLE_HEIGHT - 1, fw - 1, TITLE_HEIGHT - 1);
/* title text */
int btn_area = BTN_SIZE * 3 + BTN_MARGIN * 3 + BORDER_W + 8;
int text_max_w = fw - BORDER_W * 2 - btn_area - 8;
if (text_max_w < 10) text_max_w = 10;
XftDraw *xd = XftDrawCreate(wm->dpy, c->frame, wm->visual, wm->cmap);
if (xd) {
XftColor col_text;
xft_color(wm, c->focused ? COLOR_TITLE_TEXT_ACT : COLOR_TITLE_TEXT_INACT, &col_text);
XRectangle clip = { BORDER_W + 4, 1, (unsigned short)text_max_w, TITLE_HEIGHT - 2 };
XftDrawSetClipRectangles(xd, 0, 0, &clip, 1);
XGlyphInfo ext;
XftTextExtentsUtf8(wm->dpy, wm->font_title,
(const FcChar8 *)c->title, strlen(c->title), &ext);
int ty = (TITLE_HEIGHT - ext.height) / 2 + ext.height - ext.y;
XftDrawStringUtf8(xd, &col_text, wm->font_title,
BORDER_W + 6, ty,
(const FcChar8 *)c->title, strlen(c->title));
XftColorFree(wm->dpy, wm->visual, wm->cmap, &col_text);
XftDrawDestroy(xd);
}
frame_draw_buttons(wm, c);
draw_resize_grip(wm, c);
}
+302
View File
@@ -0,0 +1,302 @@
#include "wm.h"
/* ── helpers ─────────────────────────────────────────────────────────── */
static void send_delete(WM *wm, Window win) {
XEvent ev = {0};
ev.xclient.type = ClientMessage;
ev.xclient.window = win;
ev.xclient.message_type = wm->wm_protocols;
ev.xclient.format = 32;
ev.xclient.data.l[0] = wm->wm_delete_window;
ev.xclient.data.l[1] = CurrentTime;
XSendEvent(wm->dpy, win, False, NoEventMask, &ev);
}
static void set_cursor(WM *wm, Client *c, int x, int y) {
Cursor cur;
if (!c->maximized && frame_hit_resize(c, x, y)) cur = wm->cur_resize;
else if (y < TITLE_HEIGHT) cur = wm->cur_move;
else cur = wm->cur_normal;
XDefineCursor(wm->dpy, c->frame, cur);
}
/* ── snap on drag release ────────────────────────────────────────────── */
static void check_snap(WM *wm, Client *c) {
int sw = DisplayWidth(wm->dpy, wm->screen);
int sh = DisplayHeight(wm->dpy, wm->screen);
if (c->x <= SNAP_DIST) {
if (c->y <= SNAP_DIST) {
client_maximize(wm, c);
} else {
client_snap_left(wm, c);
}
} else if (c->x + (int)(c->w + BORDER_W * 2) >= sw - 185 - SNAP_DIST) {
client_snap_right(wm, c);
} else if (c->y <= SNAP_DIST) {
client_maximize(wm, c);
} else {
(void)sh;
}
}
/* ── event handlers ──────────────────────────────────────────────────── */
void on_map_request(WM *wm, XMapRequestEvent *e) {
client_add(wm, e->window);
}
void on_unmap_notify(WM *wm, XUnmapEvent *e) {
if (e->send_event) return;
Client *c = client_find_by_win(wm, e->window);
if (c && !c->minimized) client_remove(wm, e->window);
}
void on_destroy_notify(WM *wm, XDestroyWindowEvent *e) {
Client *c = client_find_by_win(wm, e->window);
if (!c) c = client_find_by_frame(wm, e->window);
if (c) client_remove(wm, c->win);
}
void on_expose(WM *wm, XExposeEvent *e) {
if (e->count != 0) return;
if (wm->menu_visible && e->window == wm->menu_win) {
menu_draw(wm);
return;
}
Client *c = client_find_by_frame(wm, e->window);
if (c && !c->minimized) frame_draw(wm, c);
}
void on_configure_request(WM *wm, XConfigureRequestEvent *e) {
Client *c = client_find_by_win(wm, e->window);
XWindowChanges wc;
wc.x = e->x; wc.y = e->y;
wc.width = e->width; wc.height = e->height;
wc.border_width = e->border_width;
wc.sibling = e->above; wc.stack_mode = e->detail;
if (c && !c->maximized) {
c->w = e->width; c->h = e->height;
XResizeWindow(wm->dpy, c->frame,
c->w + BORDER_W*2, c->h + TITLE_HEIGHT + BORDER_W);
}
XConfigureWindow(wm->dpy, e->window, e->value_mask, &wc);
}
void on_enter_notify(WM *wm, XCrossingEvent *e) {
Client *c = client_find_by_frame(wm, e->window);
if (c && !c->minimized) set_cursor(wm, c, e->x, e->y);
}
void on_client_message(WM *wm, XClientMessageEvent *e) {
Atom net_active = XInternAtom(wm->dpy, "_NET_ACTIVE_WINDOW", False);
if (e->message_type == net_active) {
Client *c = client_find_by_win(wm, e->window);
if (c) client_restore(wm, c);
}
}
/* Called when a managed window changes a property (title, etc.) */
void on_property_notify(WM *wm, XPropertyEvent *e) {
Client *c = client_find_by_win(wm, e->window);
if (!c || c->minimized) return;
Atom wm_name = XInternAtom(wm->dpy, "WM_NAME", False);
Atom net_wm_name = XInternAtom(wm->dpy, "_NET_WM_NAME", False);
Atom utf8 = XInternAtom(wm->dpy, "UTF8_STRING", False);
if (e->atom != wm_name && e->atom != net_wm_name) return;
/* re-fetch title */
char new_title[256] = {0};
{
Atom type; int fmt; unsigned long n, after;
unsigned char *data = NULL;
if (XGetWindowProperty(wm->dpy, c->win, net_wm_name, 0, 256, False,
utf8, &type, &fmt, &n, &after, &data)
== Success && data && n > 0) {
snprintf(new_title, 255, "%.*s", (int)n, data);
XFree(data);
} else {
if (data) XFree(data);
char *name = NULL;
if (XFetchName(wm->dpy, c->win, &name) && name) {
strncpy(new_title, name, 255);
XFree(name);
}
}
}
if (new_title[0] && strcmp(new_title, c->title) != 0) {
strncpy(c->title, new_title, 255);
frame_draw(wm, c);
}
}
void on_button_press(WM *wm, XButtonEvent *e) {
/* menu window */
if (wm->menu_visible && e->window == wm->menu_win) {
if (e->button == Button1) menu_click(wm, e->x, e->y);
else menu_hide(wm);
return;
}
/* right-click root → menu */
if (e->window == wm->root && e->button == Button3) {
menu_show(wm, e->x_root, e->y_root);
return;
}
if (wm->menu_visible) { menu_hide(wm); return; }
Client *c = client_find_by_frame(wm, e->window);
if (!c || c->minimized) return;
client_focus(wm, c);
if (e->button != Button1) return;
if (frame_hit_close(c, e->x, e->y)) {
send_delete(wm, c->win);
return;
}
if (frame_hit_maximize(c, e->x, e->y)) {
client_toggle_maximize(wm, c);
return;
}
if (frame_hit_minimize(c, e->x, e->y)) {
client_minimize(wm, c);
return;
}
if (!c->maximized && frame_hit_resize(c, e->x, e->y)) {
wm->drag_mode = DRAG_RESIZE;
wm->drag_frame = c->frame;
wm->drag_start_x = e->x_root;
wm->drag_start_y = e->y_root;
wm->drag_win_w = c->w;
wm->drag_win_h = c->h;
XGrabPointer(wm->dpy, c->frame, False,
ButtonReleaseMask | PointerMotionMask,
GrabModeAsync, GrabModeAsync,
None, wm->cur_resize, CurrentTime);
return;
}
if (e->y < TITLE_HEIGHT) {
/* double-click title bar → maximize toggle */
if (e->time - c->last_click_time < DBL_CLICK_MS) {
client_toggle_maximize(wm, c);
c->last_click_time = 0;
return;
}
c->last_click_time = e->time;
if (c->maximized) return; /* don't drag a maximized window */
wm->drag_mode = DRAG_MOVE;
wm->drag_frame = c->frame;
wm->drag_start_x = e->x_root;
wm->drag_start_y = e->y_root;
wm->drag_win_x = c->x;
wm->drag_win_y = c->y;
XGrabPointer(wm->dpy, c->frame, False,
ButtonReleaseMask | PointerMotionMask,
GrabModeAsync, GrabModeAsync,
None, wm->cur_move, CurrentTime);
}
}
void on_button_release(WM *wm, XButtonEvent *e) {
(void)e;
if (wm->drag_mode == DRAG_MOVE) {
/* snap check after move */
Client *c = client_find_by_frame(wm, wm->drag_frame);
if (c) check_snap(wm, c);
}
if (wm->drag_mode != DRAG_NONE) {
wm->drag_mode = DRAG_NONE;
XUngrabPointer(wm->dpy, CurrentTime);
}
}
void on_motion_notify(WM *wm, XMotionEvent *e) {
if (wm->menu_visible && e->window == wm->menu_win) {
int idx = menu_item_at(e->y);
if (idx != wm->menu_hovered) {
wm->menu_hovered = idx;
menu_draw(wm);
}
return;
}
if (wm->drag_mode == DRAG_NONE) {
Client *c = client_find_by_frame(wm, e->window);
if (c) set_cursor(wm, c, e->x, e->y);
return;
}
Client *c = client_find_by_frame(wm, wm->drag_frame);
if (!c) return;
int dx = e->x_root - wm->drag_start_x;
int dy = e->y_root - wm->drag_start_y;
if (wm->drag_mode == DRAG_MOVE) {
c->x = wm->drag_win_x + dx;
c->y = wm->drag_win_y + dy;
if (c->y < 0) c->y = 0; /* don't drag above screen top */
XMoveWindow(wm->dpy, c->frame, c->x, c->y);
} else {
int nw = (int)wm->drag_win_w + dx;
int nh = (int)wm->drag_win_h + dy;
if (nw < MIN_WIN_W) nw = MIN_WIN_W;
if (nh < MIN_WIN_H) nh = MIN_WIN_H;
c->w = (unsigned)nw; c->h = (unsigned)nh;
XResizeWindow(wm->dpy, c->frame, c->w+BORDER_W*2, c->h+TITLE_HEIGHT+BORDER_W);
XResizeWindow(wm->dpy, c->win, c->w, c->h);
frame_draw(wm, c);
}
}
void on_key_press(WM *wm, XKeyEvent *e) {
KeySym sym = XLookupKeysym(e, 0);
/* Alt+F4 — close focused window */
if ((e->state & Mod1Mask) && sym == XK_F4 && wm->focused)
send_delete(wm, wm->focused->win);
/* Alt+Tab — cycle windows */
if ((e->state & Mod1Mask) && sym == XK_Tab && wm->clients) {
Client *start = wm->focused ? wm->focused->next : wm->clients;
Client *n = start;
while (n && n->minimized) n = n->next;
if (!n) { n = wm->clients; while (n && n->minimized) n = n->next; }
if (n && n != wm->focused) client_focus(wm, n);
}
/* Alt+F9 — minimize */
if ((e->state & Mod1Mask) && sym == XK_F9 && wm->focused)
client_minimize(wm, wm->focused);
/* Alt+F10 — maximize toggle */
if ((e->state & Mod1Mask) && sym == XK_F10 && wm->focused)
client_toggle_maximize(wm, wm->focused);
/* Alt+F1 — launcher */
if ((e->state & Mod1Mask) && sym == XK_F1)
system("skylauncher &");
/* Super+Left — snap left */
if ((e->state & Mod4Mask) && sym == XK_Left && wm->focused)
client_snap_left(wm, wm->focused);
/* Super+Right — snap right */
if ((e->state & Mod4Mask) && sym == XK_Right && wm->focused)
client_snap_right(wm, wm->focused);
/* Super+Up — maximize */
if ((e->state & Mod4Mask) && sym == XK_Up && wm->focused)
client_maximize(wm, wm->focused);
/* Super+Down — unmaximize / restore */
if ((e->state & Mod4Mask) && sym == XK_Down && wm->focused)
client_unmaximize(wm, wm->focused);
/* Escape — hide menu */
if (sym == XK_Escape && wm->menu_visible)
menu_hide(wm);
}
+160
View File
@@ -0,0 +1,160 @@
#include "wm.h"
#define MENU_ITEM_H 24
#define MENU_W 220
#define MENU_PADDING 5
static const MenuItem MENU_ITEMS[] = {
{ "App Launcher", "skylauncher &" },
{ "Terminal", "xterm &" },
{ "File Manager", "thunar &" },
{ "Text Editor", "mousepad &" },
{ "──────────────────", NULL },
{ "Web Browser", "firefox &" },
{ "System Monitor", "xterm -T 'System Monitor' -e htop &" },
{ "Volume Control", "pavucontrol &" },
{ "Display Settings", "arandr &" },
{ "──────────────────", NULL },
{ "Change Wallpaper", "skywallpaper --reset &" },
{ "──────────────────", NULL },
{ "Restart SkyWM", "pkill skywm; sleep 0.2; skywm &" },
{ "Quit SkyOS", "pkill skywm" },
};
#define MENU_COUNT ((int)(sizeof(MENU_ITEMS) / sizeof(MENU_ITEMS[0])))
static int is_separator(int i) {
return MENU_ITEMS[i].cmd == NULL;
}
static int menu_height(void) {
int h = MENU_PADDING;
for (int i = 0; i < MENU_COUNT; i++)
h += is_separator(i) ? 8 : MENU_ITEM_H;
return h + MENU_PADDING;
}
void menu_show(WM *wm, int x, int y) {
if (wm->menu_visible) menu_hide(wm);
int sw = DisplayWidth(wm->dpy, wm->screen);
int sh = DisplayHeight(wm->dpy, wm->screen);
int mh = menu_height();
if (x + MENU_W > sw) x = sw - MENU_W - 4;
if (y + mh > sh) y = sh - mh - 4;
wm->menu_x = x;
wm->menu_y = y;
wm->menu_hovered = -1;
XSetWindowAttributes swa;
swa.override_redirect = True;
swa.background_pixel = color_alloc(wm, 0x1C2030); /* dark menu bg */
swa.event_mask = ExposureMask | ButtonPressMask
| ButtonReleaseMask | PointerMotionMask
| LeaveWindowMask;
swa.save_under = True;
wm->menu_win = XCreateWindow(
wm->dpy, wm->root,
x, y, MENU_W, mh, 1,
CopyFromParent, InputOutput, CopyFromParent,
CWOverrideRedirect | CWBackPixel | CWEventMask | CWSaveUnder,
&swa
);
/* 1px accent border */
XSetWindowBorder(wm->dpy, wm->menu_win, color_alloc(wm, COLOR_BORDER_LIGHT));
XMapRaised(wm->dpy, wm->menu_win);
wm->menu_visible = 1;
/* Grab pointer so clicks outside hide the menu */
XGrabPointer(wm->dpy, wm->menu_win, False,
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
GrabModeAsync, GrabModeAsync,
None, None, CurrentTime);
}
void menu_hide(WM *wm) {
if (!wm->menu_visible) return;
XUngrabPointer(wm->dpy, CurrentTime);
XDestroyWindow(wm->dpy, wm->menu_win);
wm->menu_win = 0;
wm->menu_visible = 0;
}
static int item_y(int idx) {
int y = MENU_PADDING;
for (int i = 0; i < idx; i++)
y += is_separator(i) ? 8 : MENU_ITEM_H;
return y;
}
int menu_item_at(int py) {
int y = MENU_PADDING;
for (int i = 0; i < MENU_COUNT; i++) {
int h = is_separator(i) ? 8 : MENU_ITEM_H;
if (py >= y && py < y + h) return i;
y += h;
}
return -1;
}
void menu_draw(WM *wm) {
if (!wm->menu_visible) return;
XftDraw *xd = XftDrawCreate(wm->dpy, wm->menu_win, wm->visual, wm->cmap);
if (!xd) return;
XftColor col_normal, col_hover_bg, col_hover_text, col_sep;
xft_color(wm, 0xC8D4E8, &col_normal); /* light text on dark bg */
xft_color(wm, 0x2060A0, &col_hover_bg); /* blue highlight */
xft_color(wm, 0xF0F4FF, &col_hover_text);
xft_color(wm, 0x303848, &col_sep);
/* clear background */
XSetForeground(wm->dpy, wm->gc, color_alloc(wm, 0x1C2030));
XFillRectangle(wm->dpy, wm->menu_win, wm->gc, 0, 0, MENU_W, menu_height());
for (int i = 0; i < MENU_COUNT; i++) {
int iy = item_y(i);
if (is_separator(i)) {
XSetForeground(wm->dpy, wm->gc, color_alloc(wm, 0x2A3040));
XDrawLine(wm->dpy, wm->menu_win, wm->gc, 8, iy + 4, MENU_W - 8, iy + 4);
continue;
}
/* hover highlight */
if (i == wm->menu_hovered && wm->menu_hovered >= 0) {
XSetForeground(wm->dpy, wm->gc, color_alloc(wm, 0x4A6888));
XFillRectangle(wm->dpy, wm->menu_win, wm->gc,
2, iy, MENU_W - 4, MENU_ITEM_H);
}
XftColor *tc = (i == wm->menu_hovered) ? &col_hover_text : &col_normal;
XGlyphInfo ext;
XftTextExtentsUtf8(wm->dpy, wm->font_menu,
(const FcChar8 *)MENU_ITEMS[i].label,
strlen(MENU_ITEMS[i].label), &ext);
int ty = iy + (MENU_ITEM_H - ext.height) / 2 + ext.height - ext.y;
XftDrawStringUtf8(xd, tc, wm->font_menu,
MENU_PADDING + 4, ty,
(const FcChar8 *)MENU_ITEMS[i].label,
strlen(MENU_ITEMS[i].label));
}
XftColorFree(wm->dpy, wm->visual, wm->cmap, &col_normal);
XftColorFree(wm->dpy, wm->visual, wm->cmap, &col_hover_bg);
XftColorFree(wm->dpy, wm->visual, wm->cmap, &col_hover_text);
XftColorFree(wm->dpy, wm->visual, wm->cmap, &col_sep);
XftDrawDestroy(xd);
}
void menu_click(WM *wm, int px, int py) { (void)px;
int i = menu_item_at(py);
if (i < 0 || is_separator(i)) { menu_hide(wm); return; }
menu_hide(wm);
const char *cmd = MENU_ITEMS[i].cmd;
if (cmd) { system(cmd); } /* intentional: user-defined launch commands */
}
+166
View File
@@ -0,0 +1,166 @@
#include "wm.h"
static int x_error_handler(Display *dpy, XErrorEvent *e) {
if (e->error_code == BadWindow || e->error_code == BadAccess)
return 0;
char buf[256];
XGetErrorText(dpy, e->error_code, buf, sizeof(buf));
fprintf(stderr, "skywm: X error: %s (request %d)\n", buf, e->request_code);
return 0;
}
static int x_error_start(Display *dpy, XErrorEvent *e) {
(void)dpy; (void)e;
fprintf(stderr, "skywm: another window manager is already running\n");
exit(1);
}
void wm_init(WM *wm) {
wm->dpy = XOpenDisplay(NULL);
if (!wm->dpy) { fprintf(stderr, "skywm: cannot open display\n"); exit(1); }
wm->screen = DefaultScreen(wm->dpy);
wm->root = RootWindow(wm->dpy, wm->screen);
wm->cmap = DefaultColormap(wm->dpy, wm->screen);
wm->visual = DefaultVisual(wm->dpy, wm->screen);
wm->depth = DefaultDepth(wm->dpy, wm->screen);
wm->gc = XCreateGC(wm->dpy, wm->root, 0, NULL);
/* fonts */
wm->font_title = XftFontOpenName(wm->dpy, wm->screen, FONT_TITLE);
if (!wm->font_title)
wm->font_title = XftFontOpenName(wm->dpy, wm->screen, "fixed:size=9");
wm->font_menu = XftFontOpenName(wm->dpy, wm->screen, FONT_MENU);
if (!wm->font_menu) wm->font_menu = wm->font_title;
/* cursors */
wm->cur_normal = XCreateFontCursor(wm->dpy, XC_left_ptr);
wm->cur_move = XCreateFontCursor(wm->dpy, XC_fleur);
wm->cur_resize = XCreateFontCursor(wm->dpy, XC_bottom_right_corner);
XDefineCursor(wm->dpy, wm->root, wm->cur_normal);
/* atoms */
wm->wm_protocols = XInternAtom(wm->dpy, "WM_PROTOCOLS", False);
wm->wm_delete_window = XInternAtom(wm->dpy, "WM_DELETE_WINDOW", False);
wm->wm_state = XInternAtom(wm->dpy, "WM_STATE", False);
wm->net_wm_name = XInternAtom(wm->dpy, "_NET_WM_NAME", False);
wm->net_wm_state = XInternAtom(wm->dpy, "_NET_WM_STATE", False);
wm->net_wm_state_hidden = XInternAtom(wm->dpy, "_NET_WM_STATE_HIDDEN", False);
wm->net_wm_state_maximized_vert = XInternAtom(wm->dpy, "_NET_WM_STATE_MAXIMIZED_VERT", False);
wm->net_wm_state_maximized_horz = XInternAtom(wm->dpy, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
/* advertise basic EWMH support */
Atom net_supported = XInternAtom(wm->dpy, "_NET_SUPPORTED", False);
Atom supported[] = {
wm->net_wm_state, wm->net_wm_state_hidden,
wm->net_wm_state_maximized_vert,
wm->net_wm_state_maximized_horz,
XInternAtom(wm->dpy, "_NET_ACTIVE_WINDOW", False),
XInternAtom(wm->dpy, "_NET_CLIENT_LIST", False),
XInternAtom(wm->dpy, "_NET_NUMBER_OF_DESKTOPS", False),
XInternAtom(wm->dpy, "_NET_CURRENT_DESKTOP", False),
XInternAtom(wm->dpy, "_NET_WM_NAME", False),
};
XChangeProperty(wm->dpy, wm->root, net_supported, XA_ATOM, 32,
PropModeReplace, (unsigned char *)supported,
sizeof(supported)/sizeof(Atom));
/* set initial desktop count */
Atom net_ndesktops = XInternAtom(wm->dpy, "_NET_NUMBER_OF_DESKTOPS", False);
long ndesktops = 4;
XChangeProperty(wm->dpy, wm->root, net_ndesktops, XA_CARDINAL, 32,
PropModeReplace, (unsigned char *)&ndesktops, 1);
Atom net_curdesk = XInternAtom(wm->dpy, "_NET_CURRENT_DESKTOP", False);
long curdesk = 0;
XChangeProperty(wm->dpy, wm->root, net_curdesk, XA_CARDINAL, 32,
PropModeReplace, (unsigned char *)&curdesk, 1);
/* detect conflicting WM */
XSetErrorHandler(x_error_start);
XSelectInput(wm->dpy, wm->root,
SubstructureRedirectMask | SubstructureNotifyMask |
KeyPressMask | ButtonPressMask);
XSync(wm->dpy, False);
XSetErrorHandler(x_error_handler);
/* desktop background — feh will overwrite this if a wallpaper is set */
XSetWindowBackground(wm->dpy, wm->root, color_alloc(wm, COLOR_BG));
XClearWindow(wm->dpy, wm->root);
/* global key grabs */
struct { KeySym sym; unsigned mod; } grabs[] = {
{ XK_Tab, Mod1Mask },
{ XK_F4, Mod1Mask },
{ XK_F9, Mod1Mask }, /* minimize */
{ XK_F10, Mod1Mask }, /* maximize */
{ XK_F1, Mod1Mask }, /* launcher */
{ XK_Left, Mod4Mask }, /* snap left */
{ XK_Right, Mod4Mask }, /* snap right */
{ XK_Up, Mod4Mask }, /* maximize */
{ XK_Down, Mod4Mask }, /* restore */
{ XK_Escape, 0 },
};
for (int i = 0; i < (int)(sizeof(grabs)/sizeof(grabs[0])); i++) {
KeyCode kc = XKeysymToKeycode(wm->dpy, grabs[i].sym);
XGrabKey(wm->dpy, kc, grabs[i].mod, wm->root,
True, GrabModeAsync, GrabModeAsync);
}
/* add Alt+F1 to key handler */
/* adopt pre-existing windows */
Window root_ret, parent_ret, *children;
unsigned int nchildren;
XQueryTree(wm->dpy, wm->root, &root_ret, &parent_ret, &children, &nchildren);
for (unsigned int i = 0; i < nchildren; i++) {
XWindowAttributes wa;
XGetWindowAttributes(wm->dpy, children[i], &wa);
if (!wa.override_redirect && wa.map_state == IsViewable)
client_add(wm, children[i]);
}
if (children) XFree(children);
XSync(wm->dpy, False);
fprintf(stderr, "skywm: started\n");
}
void wm_run(WM *wm) {
XEvent e;
for (;;) {
XNextEvent(wm->dpy, &e);
switch (e.type) {
case MapRequest: on_map_request(wm, &e.xmaprequest); break;
case UnmapNotify: on_unmap_notify(wm, &e.xunmap); break;
case DestroyNotify: on_destroy_notify(wm, &e.xdestroywindow); break;
case ConfigureRequest: on_configure_request(wm, &e.xconfigurerequest); break;
case Expose: on_expose(wm, &e.xexpose); break;
case ButtonPress: on_button_press(wm, &e.xbutton); break;
case ButtonRelease: on_button_release(wm, &e.xbutton); break;
case MotionNotify: on_motion_notify(wm, &e.xmotion); break;
case KeyPress: on_key_press(wm, &e.xkey); break;
case EnterNotify: on_enter_notify(wm, &e.xcrossing); break;
case ClientMessage: on_client_message(wm, &e.xclient); break;
case PropertyNotify: on_property_notify(wm, &e.xproperty); break;
default: break;
}
}
}
void wm_cleanup(WM *wm) {
if (wm->font_title) XftFontClose(wm->dpy, wm->font_title);
if (wm->font_menu && wm->font_menu != wm->font_title)
XftFontClose(wm->dpy, wm->font_menu);
XFreeCursor(wm->dpy, wm->cur_normal);
XFreeCursor(wm->dpy, wm->cur_move);
XFreeCursor(wm->dpy, wm->cur_resize);
XFreeGC(wm->dpy, wm->gc);
XCloseDisplay(wm->dpy);
}
int main(void) {
WM wm = {0};
wm_init(&wm);
wm_run(&wm);
wm_cleanup(&wm);
return 0;
}