This commit is contained in:
Nishi 2025-12-04 06:45:45 +09:00
commit 6b05ca3263
Signed by: nishi
GPG key ID: 27EF69B208EB9343
7 changed files with 416 additions and 3 deletions

View file

@ -1,4 +1,3 @@
#include <stdlib.h>
#define _MILSKO
#define USE_X11
#include "mdm.h"
@ -53,6 +52,7 @@ static int wm_detect(Display* disp, XErrorEvent* ev) {
static void* x11_thread_routine(void* arg) {
loop_x();
XCloseDisplay(xdisplay);
return NULL;
}
int init_x(void) {
@ -114,9 +114,10 @@ void loop_x(void) {
pthread_mutex_lock(&xmutex);
for(i = 0; i < arrlen(frames); i++) {
if(frames[i].frame->lowlevel->x11.window == ev.xmaprequest.window) {
XReparentWindow(xdisplay, frames[i].client, frames[i].frame->lowlevel->x11.window, MwDefaultBorderWidth(root), MwDefaultBorderWidth(root));
XMapWindow(xdisplay, ev.xmaprequest.window);
XMapWindow(xdisplay, frames[i].client);
XReparentWindow(xdisplay, frames[i].client, frames[i].frame->lowlevel->x11.window, MwDefaultBorderWidth(root), MwDefaultBorderWidth(root));
XFlush(xdisplay);
break;
}
}

View file

@ -1,2 +1,12 @@
include(MDEBase)
include(MDEPkgConfig)
setup_project(milkwm ${CMAKE_INSTALL_BINDIR})
pkg_config_add(milkwm x11)
pkg_config_add(milkwm inih)
target_link_libraries(
milkwm
PRIVATE
pthread
)

View file

@ -1 +1,9 @@
int main() {}
#include "milkwm.h"
int main() {
int st;
if((st = init_x()) != 0) return st;
loop_wm();
}

33
milkwm/milkwm.h Normal file
View file

@ -0,0 +1,33 @@
#ifndef __MILKWM_H__
#define __MILKWM_H__
#include "config.h"
/* Milsko */
#include <MDE/Foundation.h>
#include <Mw/Milsko.h>
/* External */
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
/* Standard */
#include <pthread.h>
#define TitleBarHeight 16
/* xserver.c */
extern pthread_t xthread;
extern pthread_mutex_t xmutex;
extern Display* xdisplay;
int init_x(void);
void loop_x(void);
/* wm.c */
void loop_wm(void);
MwWidget wm_frame(int w, int h);
void wm_set_name(MwWidget widget, const char* name);
#endif

2
milkwm/stb_ds.c Normal file
View file

@ -0,0 +1,2 @@
#define STB_DS_IMPLEMENTATION
#include <stb_ds.h>

78
milkwm/wm.c Normal file
View file

@ -0,0 +1,78 @@
#define _MILSKO
#include "milkwm.h"
static MwWidget root;
typedef struct wmframe {
MwWidget titlebar;
} wmframe_t;
void loop_wm(void) {
void* out;
pthread_mutex_lock(&xmutex);
MwLibraryInit();
root = MwCreateWidget(NULL, "root", NULL, 0, 0, 0, 0);
pthread_mutex_unlock(&xmutex);
while(1) {
int s = 0;
pthread_mutex_lock(&xmutex);
while(MwPending(root)) {
if((s = MwStep(root)) != 0) break;
}
pthread_mutex_unlock(&xmutex);
if(s != 0) break;
MwTimeSleep(30);
}
pthread_join(xthread, &out);
}
static void resize(MwWidget handle, void* user, void* client) {
wmframe_t* f = handle->opaque;
int ww = MwGetInteger(handle, MwNwidth);
int wh = MwGetInteger(handle, MwNheight);
MwVaApply(f->titlebar,
MwNx, MwDefaultBorderWidth(handle),
MwNy, MwDefaultBorderWidth(handle),
MwNwidth, ww - MwDefaultBorderWidth(handle) * 2,
MwNheight, TitleBarHeight + MwDefaultBorderWidth(handle) * 2,
NULL);
}
MwWidget wm_frame(int w, int h) {
MwWidget wnd, titlebar;
int i;
wmframe_t* f = malloc(sizeof(*f));
pthread_mutex_lock(&xmutex);
wnd = MwVaCreateWidget(MwWindowClass, "frame", root, MwDEFAULT, MwDEFAULT, w + MwDefaultBorderWidth(root) * 2, h + MwDefaultBorderWidth(root) * 4 + TitleBarHeight,
MwNhasBorder, 1,
MwNinverted, 0,
NULL);
wnd->opaque = f;
f->titlebar = MwVaCreateWidget(MwFrameClass, "titlebar", wnd, 0, 0, 0, 0,
MwNhasBorder, 1,
MwNinverted, 1,
NULL);
MwCreateWidget(MwButtonClass, "button", f->titlebar, MwDefaultBorderWidth(wnd), MwDefaultBorderWidth(wnd), TitleBarHeight, TitleBarHeight);
resize(wnd, NULL, NULL);
pthread_mutex_unlock(&xmutex);
return wnd;
}
void wm_set_name(MwWidget widget, const char* name) {
}

281
milkwm/xserver.c Normal file
View file

@ -0,0 +1,281 @@
#define _MILSKO
#define USE_X11
#include "milkwm.h"
#include <stb_ds.h>
Display* xdisplay;
pthread_t xthread;
pthread_mutex_t xmutex;
static int wm_detected = 0;
static int wm_detect(Display* disp, XErrorEvent* ev) {
wm_detected = 1;
}
static int ignore_error(Display* disp, XErrorEvent* ev) {
}
static void* x11_thread_routine(void* arg) {
loop_x();
XCloseDisplay(xdisplay);
return NULL;
}
static Window focus = None, nofocus;
static void set_focus(Window w) {
if(w == None) {
XSetInputFocus(xdisplay, nofocus, RevertToParent, CurrentTime);
} else {
XSetInputFocus(xdisplay, w, RevertToParent, CurrentTime);
}
focus = w;
}
int init_x(void) {
void* old;
XSetWindowAttributes xswa;
xswa.override_redirect = True;
xdisplay = XOpenDisplay(NULL);
old = XSetErrorHandler(wm_detect);
XSelectInput(xdisplay, DefaultRootWindow(xdisplay), SubstructureRedirectMask);
XSync(xdisplay, False);
XSetErrorHandler(old);
if(wm_detected) return 1;
nofocus = XCreateSimpleWindow(xdisplay, DefaultRootWindow(xdisplay), -10, -10, 1, 1, 0, 0, 0);
XChangeWindowAttributes(xdisplay, nofocus, CWOverrideRedirect, &xswa);
XMapWindow(xdisplay, nofocus);
set_focus(None);
XChangeProperty(xdisplay, DefaultRootWindow(xdisplay), XInternAtom(xdisplay, "_NET_SUPPORTING_WM_CHECK", False), XA_WINDOW, 32, PropModeReplace, (unsigned char*)&nofocus, 1);
XChangeProperty(xdisplay, nofocus, XInternAtom(xdisplay, "_NET_SUPPORTING_WM_CHECK", False), XA_WINDOW, 32, PropModeReplace, (unsigned char*)&nofocus, 1);
XChangeProperty(xdisplay, nofocus, XInternAtom(xdisplay, "_NET_WM_NAME", False), XInternAtom(xdisplay, "UTF8_STRING", False), 8, PropModeReplace, (unsigned char*)"milkwm", 6);
pthread_mutex_init(&xmutex, NULL);
pthread_create(&xthread, NULL, x11_thread_routine, NULL);
return 0;
}
typedef struct window {
MwWidget frame;
Window client;
} window_t;
static window_t* windows = NULL;
static void save(Window w) {
XSelectInput(xdisplay, w, StructureNotifyMask | FocusChangeMask | PropertyChangeMask);
XSetWindowBorderWidth(xdisplay, w, 0);
XAddToSaveSet(xdisplay, w);
}
int parent_eq(Window wnd, Window might_be_parent) {
Window root, parent;
Window* children;
unsigned int nchildren;
XQueryTree(xdisplay, wnd, &root, &parent, &children, &nchildren);
if(children != NULL) XFree(children);
if(parent == might_be_parent) return 1;
if(parent != root) return parent_eq(parent, might_be_parent);
return 0;
}
void loop_x(void) {
XEvent ev;
Window root, parent;
Window* children;
unsigned int nchildren;
int i;
XQueryTree(xdisplay, DefaultRootWindow(xdisplay), &root, &parent, &children, &nchildren);
if(children != NULL) {
for(i = 0; i < nchildren; i++) {
window_t w;
XWindowAttributes xwa;
XGetWindowAttributes(xdisplay, children[i], &xwa);
if(xwa.override_redirect || xwa.map_state != IsViewable) continue;
w.frame = wm_frame(xwa.width, xwa.height);
w.client = children[i];
XUnmapWindow(xdisplay, children[i]);
save(w.client);
arrput(windows, w);
}
XFree(children);
}
while(1) {
XNextEvent(xdisplay, &ev);
if(ev.type == ButtonPress) {
if(ev.type == ButtonPress) {
for(i = 0; i < arrlen(windows); i++) {
if(windows[i].frame->lowlevel->x11.window == ev.xbutton.window || parent_eq(ev.xbutton.window, windows[i].frame->lowlevel->x11.window)) {
set_focus(windows[i].client);
break;
}
}
if(i != arrlen(windows)) continue;
set_focus(ev.xbutton.window);
}
} else if(ev.type == FocusIn && ev.xfocus.window != focus) {
set_focus(focus);
} else if(ev.type == FocusOut && focus != None) {
set_focus(focus);
} else if(ev.type == ConfigureRequest) {
XWindowChanges xwc;
int i;
Window w = ev.xconfigurerequest.window;
for(i = 0; i < arrlen(windows); i++) {
if(windows[i].client == w) {
w = windows[i].frame->lowlevel->x11.window;
break;
}
}
xwc.x = ev.xconfigurerequest.x;
xwc.y = ev.xconfigurerequest.y;
xwc.width = ev.xconfigurerequest.width;
xwc.height = ev.xconfigurerequest.height;
XConfigureWindow(xdisplay, w, CWX | CWY | CWWidth | CWHeight, &xwc);
} else if(ev.type == ConfigureNotify) {
int i;
for(i = 0; i < arrlen(windows); i++) {
if(windows[i].client == ev.xconfigure.window) {
XWindowChanges xwc;
XWindowAttributes xwa;
xwc.x = ev.xconfigure.x;
xwc.y = ev.xconfigure.y;
xwc.width = MwDefaultBorderWidth(windows[i].frame) * 2 + ev.xconfigure.width;
xwc.height = MwDefaultBorderWidth(windows[i].frame) * 4 + TitleBarHeight + ev.xconfigure.height;
XGetWindowAttributes(xdisplay, windows[i].frame->lowlevel->x11.window, &xwa);
if(xwc.x != MwDefaultBorderWidth(windows[i].frame) || xwc.y != (MwDefaultBorderWidth(windows[i].frame) * 3 + TitleBarHeight)) {
XConfigureWindow(xdisplay, windows[i].frame->lowlevel->x11.window, CWX | CWY | CWWidth | CWHeight, &xwc);
xwc.x = MwDefaultBorderWidth(windows[i].frame);
xwc.y = MwDefaultBorderWidth(windows[i].frame) * 3 + TitleBarHeight;
XConfigureWindow(xdisplay, windows[i].client, CWX | CWY, &xwc);
} else if(xwa.width != xwc.width || xwa.height != xwc.height) {
XConfigureWindow(xdisplay, windows[i].frame->lowlevel->x11.window, CWWidth | CWHeight, &xwc);
}
break;
}
}
} else if(ev.type == MapRequest) {
window_t w;
int i;
int ret = 0;
XWindowAttributes xwa;
XGetWindowAttributes(xdisplay, ev.xmaprequest.window, &xwa);
for(i = 0; i < arrlen(windows); i++) {
if(windows[i].frame->lowlevel->x11.window == ev.xmaprequest.window) break;
if(windows[i].client == ev.xmaprequest.window) {
ret = 1;
break;
}
}
if(ret) continue;
if(i != arrlen(windows)) {
XWindowAttributes xwa;
XGetWindowAttributes(xdisplay, windows[i].client, &xwa);
XMoveWindow(xdisplay, windows[i].frame->lowlevel->x11.window, xwa.x, xwa.y);
XMapWindow(xdisplay, ev.xmaprequest.window);
pthread_mutex_lock(&xmutex);
XReparentWindow(xdisplay, windows[i].client, windows[i].frame->lowlevel->x11.window, MwDefaultBorderWidth(windows[i].frame), MwDefaultBorderWidth(windows[i].frame) * 3 + TitleBarHeight);
pthread_mutex_unlock(&xmutex);
XMapWindow(xdisplay, windows[i].client);
XFlush(xdisplay);
continue;
}
w.frame = wm_frame(xwa.width, xwa.height);
w.client = ev.xmaprequest.window;
save(w.client);
arrput(windows, w);
} else if(ev.type == PropertyNotify) {
int i;
for(i = 0; i < arrlen(windows); i++) {
if(windows[i].client == ev.xproperty.window) {
break;
}
}
if(arrlen(windows) == i) continue;
if(ev.xproperty.atom == XInternAtom(xdisplay, "WM_NAME", False)) {
Atom type;
int format;
unsigned long nitem, after;
unsigned char* buf;
if(XGetWindowProperty(xdisplay, ev.xproperty.window, ev.xproperty.atom, 0, 1024, False, XA_STRING, &type, &format, &nitem, &after, &buf) == Success) {
wm_set_name(windows[i].frame, buf);
XFree(buf);
}
}
} else if(ev.type == MapNotify) {
int i;
for(i = 0; i < arrlen(windows); i++) {
if(windows[i].client == ev.xmap.window) {
set_focus(windows[i].client);
break;
}
}
} else if(ev.type == UnmapNotify) {
int i;
for(i = 0; i < arrlen(windows); i++) {
if(windows[i].client == ev.xunmap.window) {
void* old;
XWindowAttributes xwa;
XGetWindowAttributes(xdisplay, windows[i].frame->lowlevel->x11.window, &xwa);
pthread_mutex_lock(&xmutex);
old = XSetErrorHandler(ignore_error);
XReparentWindow(xdisplay, windows[i].client, DefaultRootWindow(xdisplay), xwa.x, xwa.y);
XSync(xdisplay, False);
XSetErrorHandler(old);
MwDestroyWidget(windows[i].frame);
pthread_mutex_unlock(&xmutex);
arrdel(windows, i);
break;
}
}
if(focus == ev.xunmap.window) {
Window w = None;
int i;
for(i = arrlen(windows) - 1; i >= 0; i--) {
XWindowAttributes xwa;
XGetWindowAttributes(xdisplay, windows[i].client, &xwa);
if(xwa.map_state == IsViewable) {
w = windows[i].client;
}
}
set_focus(w);
}
}
}
}