This commit is contained in:
IoIxD 2025-11-18 18:47:11 -07:00
commit 5634b5d5d7
17 changed files with 731 additions and 0 deletions

8
CMakeLists.txt Normal file
View file

@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.25)
project(
mdebase
)
add_subdirectory(mdm)
add_subdirectory(milkwm)
add_subdirectory(mpanel)

24
LICENSE Normal file
View file

@ -0,0 +1,24 @@
Copyright (c) 2025, Milsko Desktop Experience development team
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

1
README.md Normal file
View file

@ -0,0 +1 @@
# Base package for MDE

29
mdm/CMakeLists.txt Normal file
View file

@ -0,0 +1,29 @@
include(MDEBase)
include(MDEPkgConfig)
setup_project(mdm ${CMAKE_INSTALL_SBINDIR})
pkg_config_add(mdm x11)
pkg_config_add(mdm inih)
target_link_libraries(
mdm
PRIVATE
pam pthread
)
include(GNUInstallDirs)
configure_file(mdmrc.in mdmrc)
install(
FILES pam
RENAME mdm
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/examples/pam.d
)
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/mdmrc
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/examples/mdm
)
install(
FILES mdm.png
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/mdm
)

19
mdm/config.c Normal file
View file

@ -0,0 +1,19 @@
#include "mdm.h"
#include <ini.h>
char* config_picture = NULL;
static int dumper(void* user, const char* section, const char* name, const char* value) {
(void)user;
if(strcmp(section, "MDM Config") != 0) return 1;
if(strcmp(name, "Picture") == 0) config_picture = MDEStringDuplicate(value);
return 1;
}
void parse_config(void) {
ini_parse(CONFDIR "/mdm/mdmrc", dumper, NULL);
}

274
mdm/login.c Normal file
View file

@ -0,0 +1,274 @@
#include "mdm.h"
#include <ini.h>
#include <sched.h>
#include <security/_pam_types.h>
#include <security/pam_appl.h>
#include <stb_ds.h>
#include <stdlib.h>
#include <string.h>
MwWidget root;
MwWidget window, sesscombo, usercombo, passentry, error;
typedef struct session {
char* key;
session_environment_t* value;
} session_t;
session_t* sessions = NULL;
char* name_line = NULL;
char* try_exec_line = NULL;
char* exec_line = NULL;
char* desktop_names_line = NULL;
static void add_user(const char* name, void* user) {
MwComboBoxAdd(user, -1, name);
}
static int dumper(void* user, const char* section, const char* name, const char* value) {
if(strcmp(section, "Desktop Entry") != 0) return 1;
if(strcmp(name, "Name") == 0) {
name_line = MDEStringDuplicate(value);
} else if(strcmp(name, "TryExec") == 0) {
try_exec_line = MDEStringDuplicate(value);
} else if(strcmp(name, "Exec") == 0) {
exec_line = MDEStringDuplicate(value);
} else if(strcmp(name, "DesktopNames") == 0) {
desktop_names_line = MDEStringDuplicate(value);
}
return 1;
}
static void add_session(session_type_t session_type, const char* path, int dir, int symlink, void* user) {
int m = 1;
session_environment_t* value = malloc(sizeof(session_environment_t));
name_line = NULL;
exec_line = NULL;
try_exec_line = NULL;
desktop_names_line = NULL;
if(dir || symlink) return;
ini_parse(path, dumper, user);
if(name_line != NULL && (exec_line != NULL || try_exec_line != NULL)) {
int i;
for(i = 0; i < shlen(sessions); i++) {
if(strcmp(sessions[i].key, name_line) == 0) break;
}
if(i == shlen(sessions)) {
MwComboBoxAdd(user, -1, name_line);
value->run = exec_line;
value->try_run = try_exec_line;
value->session_type = session_type;
value->desktop_names = desktop_names_line;
shput(sessions, name_line, value);
m = 0;
}
}
if(name_line != NULL) free(name_line);
if(desktop_names_line != NULL) free(desktop_names_line);
if(m && try_exec_line != NULL) free(try_exec_line);
if(m && exec_line != NULL) free(exec_line);
}
static void add_session_x(const char* path, int dir, int symlink, void* user) {
add_session(MDM_SESSION_TYPE_X, path, dir, symlink, user);
}
static void add_session_wayland(const char* path, int dir, int symlink, void* user) {
add_session(MDM_SESSION_TYPE_WAYLAND, path, dir, symlink, user);
}
static int conversation(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata) {
int i;
*resp = malloc(num_msg * sizeof(struct pam_response));
for(i = 0; i < num_msg; i++) {
if(msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) {
const char* txt = MwGetText(passentry, MwNtext);
if(txt == NULL) txt = "";
resp[i]->resp_retcode = PAM_SUCCESS;
resp[i]->resp = MDEStringDuplicate(txt);
} else {
return PAM_CONV_ERR;
}
}
return PAM_SUCCESS;
}
static void try_login(MwWidget handle, void* user, void* client) {
const char* username;
const char* place;
struct pam_conv conv;
pam_handle_t* pam;
int err;
int err2;
int ret;
char err_str[255];
int print_error = 0;
int i, n;
username = MwComboBoxGet(usercombo, MwGetInteger(usercombo, MwNvalue));
conv.conv = conversation;
conv.appdata_ptr = NULL;
err = 1;
(void)handle;
(void)user;
(void)client;
ret = pam_start("mdm", username, &conv, &pam);
if(ret != PAM_SUCCESS) {
place = "Error calling pam_start";
goto cleanup;
}
ret = pam_authenticate(pam, 0);
if(ret != PAM_SUCCESS) {
place = "Error calling pam_authenticate";
goto cleanup;
}
ret = pam_acct_mgmt(pam, 0);
if(ret != PAM_SUCCESS) {
place = "Error calling pam_acct_mgmt";
goto cleanup;
}
ret = pam_open_session(pam, 0);
if(ret != PAM_SUCCESS) {
place = "Error calling pam_open_session";
goto cleanup;
}
cleanup:
pam_close_session(pam, 0);
// if pam_end fails but we have another error, we care more about the error at hand
// then whether pam successfully ended.
err2 = pam_end(pam, 0);
if(err && ret != PAM_SUCCESS) {
MwSetText(passentry, MwNtext, "");
snprintf(err_str, 255, "%s: %s", place, pam_strerror(pam, err));
print_error = 1;
} else if(err2) {
MwSetText(passentry, MwNtext, "");
snprintf(err_str, 255, "Error calling pam_end: %s", pam_strerror(pam, err2));
print_error = 1;
}
if(print_error) {
n = 0;
for(i = 0; err_str[i] != '\0' && i <= 255; i++ && n++) {
if(err_str[i] == ' ' && n >= 40) {
err_str[i] = '\n';
n = 0;
}
}
MwSetText(error, MwNtext, err_str);
} else {
printf("Logging in as %s\n", username);
struct passwd* pwd = getpwnam(username);
const char* session = MwComboBoxGet(sesscombo, MwGetInteger(sesscombo, MwNvalue));
uid = pwd->pw_uid;
gid = pwd->pw_gid;
env = shget(sessions, session);
MwDestroyWidget(root);
}
}
void login_window(void) {
MwWidget pic, userlabel, passlabel, mainsep, sesslabel, fieldsep, shutdown, reboot, ok;
MwLLPixmap p;
void* out;
int i;
pthread_mutex_lock(&xmutex);
MwLibraryInit();
root = MwCreateWidget(NULL, "root", NULL, 0, 0, 0, 0);
window = MwVaCreateWidget(MwWindowClass, "login", root, (x_width() - 366) / 2, (x_height() - 200) / 2, 366, 250,
MwNtitle, "login",
NULL);
p = config_picture == NULL ? NULL : MwLoadImage(window, config_picture);
pic = MwVaCreateWidget(MwImageClass, "image", window, 10, 10, 80, 137,
MwNhasBorder, 1,
NULL);
userlabel = MwVaCreateWidget(MwLabelClass, "userlabel", window, 95, 10, MwTextWidth(window, "User:"), 16,
MwNtext, "User:",
NULL);
usercombo = MwCreateWidget(MwComboBoxClass, "usercombo", window, 95, 10 + 16 + 5, 265, 18);
passlabel = MwVaCreateWidget(MwLabelClass, "passlabel", window, 95, 10 + 16 + 5 + 18 + 5, MwTextWidth(window, "Password:"), 16,
MwNtext, "Password:",
NULL);
passentry = MwVaCreateWidget(MwEntryClass, "passentry", window, 95, 10 + 16 + 5 + 18 + 5 + 16 + 5, 265, 18,
MwNhideInput, 1,
NULL);
mainsep = MwVaCreateWidget(MwSeparatorClass, "mainsep", window, 95, 10 + 16 + 5 + 18 + 5 + 16 + 5 + 18, 265, 10,
MwNorientation, MwHORIZONTAL,
NULL);
sesslabel = MwVaCreateWidget(MwLabelClass, "sesslabel", window, 95, 10 + 16 + 5 + 18 + 5 + 16 + 5 + 18 + 10, MwTextWidth(window, "Session Type:"), 16,
MwNtext, "Session Type:",
NULL);
sesscombo = MwCreateWidget(MwComboBoxClass, "sesscombo", window, 95, 10 + 16 + 5 + 18 + 5 + 16 + 5 + 18 + 10 + 16 + 5, 265, 18);
fieldsep = MwVaCreateWidget(MwSeparatorClass, "fieldsep", window, 10, 10 + 137, 351, 10,
MwNorientation, MwHORIZONTAL,
NULL);
shutdown = MwVaCreateWidget(MwButtonClass, "shutdown", window, 10, 10 + 137 + 10, 80, 18,
MwNtext, "Shut Down",
NULL);
reboot = MwVaCreateWidget(MwButtonClass, "reboot", window, 10 + 80 + 5, 10 + 137 + 10, 70, 18,
MwNtext, "Reboot",
NULL);
ok = MwVaCreateWidget(MwButtonClass, "ok", window, 366 - 10 - 60, 10 + 137 + 10, 60, 18,
MwNtext, "OK",
NULL);
error = MwVaCreateWidget(MwLabelClass, "error", window, 10, 10 + 137 + 10 + 20, 366, 48, NULL);
if(p != NULL) MwVaApply(pic, MwNpixmap, p, NULL);
MwAddUserHandler(ok, MwNactivateHandler, try_login, NULL);
MwAddUserHandler(passentry, MwNactivateHandler, try_login, NULL);
MDEUsersList(add_user, usercombo);
for(i = 0; i < shlen(sessions); i++) {
free(sessions[i].value);
}
shfree(sessions);
sh_new_strdup(sessions);
shdefault(sessions, NULL);
MDEDirectoryScan(DATADIR "/xsessions", add_session_x, sesscombo);
MDEDirectoryScan("/usr/share/xsessions", add_session_x, sesscombo);
MDEDirectoryScan(DATADIR "/wayland-sessions", add_session_wayland, sesscombo);
MDEDirectoryScan("/usr/share/wayland-sessions", add_session_wayland, sesscombo);
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);
}

128
mdm/main.c Normal file
View file

@ -0,0 +1,128 @@
#include "mdm.h"
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int is_launch_x = 1;
int is_daemon = 1;
gid_t gid;
uid_t uid;
session_environment_t* env;
static void set_env_standard(struct passwd* pwd);
static void set_env_xdg(struct passwd* pwd);
int main(int argc, char** argv) {
int i, st, exit;
char* backup = NULL;
pid_t pid;
for(i = 1; i < argc; i++) {
if(argv[i][0] == '-') {
if(strcmp(argv[i], "-X") == 0 || strcmp(argv[i], "--no-launch-x") == 0) {
is_launch_x = 0;
} else if(strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--no-daemon") == 0) {
is_daemon = 0;
} else {
fprintf(stderr, "%s: bad option: %s\n", argv[0], argv[i]);
return 1;
}
}
}
if(getuid() != 0) {
fprintf(stderr, "MDM has to be ran as root\n");
return 1;
}
if(is_daemon && (pid = fork()) != 0) {
FILE* f = fopen("/var/run/mdm.pid", "w");
fprintf(f, "%d\n", pid);
fclose(f);
return 0;
}
if(access(CONFDIR, F_OK) != 0) mkdir(CONFDIR, 0755);
if(access(CONFDIR "/mdm", F_OK) != 0) mkdir(CONFDIR "/mdm", 0755);
if(access(CONFDIR "/mdm/mdmrc", F_OK) != 0) MDEFileCopy(DATADIR "/examples/mdm/mdmrc", CONFDIR "/mdm/mdmrc");
parse_config();
if(getenv("DISPLAY") != NULL) backup = MDEStringDuplicate(getenv("DISPLAY"));
do {
setenv("DISPLAY", backup == NULL ? "" : backup, 1);
if(is_launch_x && (st = launch_x()) != 0) return st;
if((st = init_x()) != 0) return st;
login_window();
if(env->run == NULL && env->try_run == NULL) continue;
if((pid = fork()) == 0) {
struct passwd* pwd = getpwuid(uid);
setgid(gid);
setuid(uid);
chdir(pwd->pw_dir);
set_env_standard(pwd);
set_env_xdg(pwd);
if((exit = system(env->try_run)) != 0) {
exit = system(env->run);
} else {
_exit(exit);
}
} else {
waitpid(pid, NULL, 0);
}
if(is_launch_x) {
unsetenv("DISPLAY");
kill_x();
}
} while(is_launch_x);
if(backup != NULL) free(backup);
}
void set_env_standard(struct passwd* pwd) {
setenv("HOME", pwd->pw_dir, 1);
setenv("PWD", pwd->pw_dir, 1);
setenv("USER", pwd->pw_name, 1);
setenv("SHELL", pwd->pw_shell, 1);
setenv("LOGNAME", pwd->pw_shell, 1);
}
void set_env_xdg(struct passwd* pwd) {
// this directory isn't avaliable on bsd, but we can't check for all of them.
// so just assume it's only on linux, idek if that's true but who care
#if defined(__linux__)
char runtime_dir[255];
snprintf(runtime_dir, 255, "/run/user/%d", pwd->pw_uid);
setenv("XDG_RUNTIME_DIR", runtime_dir, 1);
#endif
switch(env->session_type) {
case MDM_SESSION_TYPE_X:
setenv("XDG_SESSION_TYPE", "x11", 1);
break;
case MDM_SESSION_TYPE_WAYLAND:
setenv("XDG_SESSION_TYPE", "wayland", 1);
break;
}
if(env->desktop_names) {
if(strlen(env->desktop_names) > 1) {
setenv("XDG_SESSION_DESKTOP", strtok(env->desktop_names, ";"), 1);
}
setenv("XDG_CURRENT_DESKTOP", env->desktop_names, 1);
}
setenv("XDG_SESSION_CLASS", "user", 1);
setenv("XDG_SESSION_ID", "1", 1);
setenv("XDG_SEAT", "seat0", 1);
// setenv("XDG_VTNR", "/dev/pts/7", 1);
}

62
mdm/mdm.h Normal file
View file

@ -0,0 +1,62 @@
#ifndef __MDM_H__
#define __MDM_H__
#include "config.h"
/* Milsko */
#include <MDE/Foundation.h>
#include <Mw/Milsko.h>
/* External */
#include <X11/Xlib.h>
/* Standard */
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <pthread.h>
/* login.c */
extern MwWidget root;
typedef enum session_type {
MDM_SESSION_TYPE_X,
MDM_SESSION_TYPE_WAYLAND,
} session_type_t;
typedef struct session_environment {
char* run;
char* try_run;
session_type_t session_type;
char* desktop_names;
} session_environment_t;
void login_window(void);
/* main.c */
extern gid_t gid;
extern uid_t uid;
extern session_environment_t* env;
/* xserver.c */
extern pthread_t xthread;
extern pthread_mutex_t xmutex;
extern Display* xdisplay;
int launch_x(void);
int x_width(void);
int x_height(void);
int init_x(void);
void loop_x(void);
void kill_x(void);
/* config.c */
extern char* config_picture;
void parse_config(void);
#endif

BIN
mdm/mdm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

2
mdm/mdmrc.in Normal file
View file

@ -0,0 +1,2 @@
[MDM Config]
Picture=@CMAKE_INSTALL_FULL_DATAROOTDIR@/mdm/mdm.png

5
mdm/pam Normal file
View file

@ -0,0 +1,5 @@
auth required pam_unix.so
auth required pam_nologin.so
account required pam_unix.so
password required pam_unix.so
session required pam_unix.so

2
mdm/stb_ds.c Normal file
View file

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

163
mdm/xserver.c Normal file
View file

@ -0,0 +1,163 @@
#include <stdlib.h>
#define _MILSKO
#define USE_X11
#include "mdm.h"
#include "stb_ds.h"
static pid_t xserver;
static int got_usr1;
Display* xdisplay;
pthread_t xthread;
pthread_mutex_t xmutex;
static void catch_usr1(int sig) {
(void)sig;
got_usr1 = 1;
}
int launch_x(void) {
got_usr1 = 0;
signal(SIGUSR1, catch_usr1);
if((xserver = fork()) == 0) {
signal(SIGUSR1, SIG_IGN);
execlp("X", "X", ":0", NULL);
_exit(-1);
} else {
while(!got_usr1) {
if(waitpid(xserver, NULL, WNOHANG) != 0) {
fprintf(stderr, "server died. what happened?\n");
return 1;
}
usleep(1000);
}
}
setenv("DISPLAY", ":0", 1);
return 0;
}
static int wm_detected = 0;
static int wm_detect(Display* disp, XErrorEvent* ev) {
(void)disp;
(void)ev;
wm_detected = 1;
}
static void* x11_thread_routine(void* arg) {
loop_x();
XCloseDisplay(xdisplay);
}
int init_x(void) {
XColor bg;
void* old;
xdisplay = XOpenDisplay(NULL);
bg.red = 0x1c * 256;
bg.green = 0x45 * 256;
bg.blue = 0x73 * 256;
XAllocColor(xdisplay, DefaultColormap(xdisplay, DefaultScreen(xdisplay)), &bg);
XSetWindowBackground(xdisplay, DefaultRootWindow(xdisplay), bg.pixel);
XClearWindow(xdisplay, DefaultRootWindow(xdisplay));
XFlush(xdisplay);
old = XSetErrorHandler(wm_detect);
XSelectInput(xdisplay, DefaultRootWindow(xdisplay), SubstructureRedirectMask);
XSync(xdisplay, False);
XSetErrorHandler(old);
if(wm_detected) {
fprintf(stderr, "WM or something seem to be running\n");
return 1;
}
pthread_mutex_init(&xmutex, NULL);
pthread_create(&xthread, NULL, x11_thread_routine, NULL);
return 0;
}
int x_width(void) {
return DisplayWidth(xdisplay, DefaultScreen(xdisplay));
}
int x_height(void) {
return DisplayHeight(xdisplay, DefaultScreen(xdisplay));
}
typedef struct window {
MwWidget frame;
Window client;
} window_t;
void loop_x(void) {
XEvent ev;
window_t* frames = NULL;
while(1) {
XNextEvent(xdisplay, &ev);
if(ev.type == MapRequest) {
XWindowAttributes xwa;
window_t w;
int i;
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);
break;
}
}
if(i != arrlen(frames)) {
pthread_mutex_unlock(&xmutex);
continue;
}
XGetWindowAttributes(xdisplay, ev.xmaprequest.window, &xwa);
w.client = ev.xmaprequest.window;
w.frame = MwVaCreateWidget(MwWindowClass, "frame", root, xwa.x - MwDefaultBorderWidth(root), xwa.y - MwDefaultBorderWidth(root), xwa.width + MwDefaultBorderWidth(root) * 2, xwa.height + MwDefaultBorderWidth(root) * 2,
MwNhasBorder, 1,
MwNinverted, 0,
NULL);
XSelectInput(xdisplay, w.client, StructureNotifyMask);
arrput(frames, w);
pthread_mutex_unlock(&xmutex);
} else if(ev.type == UnmapNotify) {
int i;
int c = 0;
pthread_mutex_lock(&xmutex);
for(i = 0; i < arrlen(frames); i++) {
if(frames[i].client == ev.xunmap.window) {
MwDestroyWidget(frames[i].frame);
arrdel(frames, i);
c = 1;
break;
}
}
pthread_mutex_unlock(&xmutex);
if(c) break;
}
}
}
void kill_x(void) {
kill(xserver, SIGINT);
waitpid(xserver, NULL, 0);
}

2
milkwm/CMakeLists.txt Normal file
View file

@ -0,0 +1,2 @@
include(MDEBase)
setup_project(milkwm ${CMAKE_INSTALL_BINDIR})

1
milkwm/main.c Normal file
View file

@ -0,0 +1 @@
int main() {}

10
mpanel/CMakeLists.txt Normal file
View file

@ -0,0 +1,10 @@
include(MDEBase)
setup_project(mpanel ${CMAKE_INSTALL_BINDIR})
if(${CMAKE_SYSTEM_NAME} STREQUAL Linux)
target_link_libraries(
mpanel
PRIVATE
dl
)
endif()

1
mpanel/main.c Normal file
View file

@ -0,0 +1 @@
int main() {}