From 2b05a449265b8f11603319b431d8cf78188568ae Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 02:05:53 +0900 Subject: [PATCH 01/29] init --- LICENSE | 24 ++++++++++++++++++++++++ README.md | 1 + 2 files changed, 25 insertions(+) create mode 100644 LICENSE create mode 100644 README.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d6a83e7 --- /dev/null +++ b/LICENSE @@ -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 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 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8764fdd --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Base package for MDE From eeee583c1bac270bdbbf619215eb216986ceea7e Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 02:50:52 +0900 Subject: [PATCH 02/29] cmake --- CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..914ad07 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.25) +project( + mdebase + LANGUAGES C + VERSION 0.0 + DESCRIPTION "MDE Base" +) From 04fdf18d0d3eb6c3ada374577051795593491d45 Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 04:15:10 +0900 Subject: [PATCH 03/29] aaa --- CMakeLists.txt | 5 ++--- milkwm/CMakeLists.txt | 20 ++++++++++++++++++++ milkwm/main.c | 1 + 3 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 milkwm/CMakeLists.txt create mode 100644 milkwm/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 914ad07..6137faa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.25) project( mdebase - LANGUAGES C - VERSION 0.0 - DESCRIPTION "MDE Base" ) + +add_subdirectory(milkwm) diff --git a/milkwm/CMakeLists.txt b/milkwm/CMakeLists.txt new file mode 100644 index 0000000..fd40150 --- /dev/null +++ b/milkwm/CMakeLists.txt @@ -0,0 +1,20 @@ +project( + milkwm +) + +file( + GLOB + milkwm_SRC + *.c +) + +add_executable( + milkwm + ${milkwm_SRC} +) + +include(GNUInstallDirs) +install( + TARGETS milkwm + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/milkwm/main.c b/milkwm/main.c new file mode 100644 index 0000000..979a1cd --- /dev/null +++ b/milkwm/main.c @@ -0,0 +1 @@ +int main(){} From 7fdf15abbb2f960a7839137ef422322646e600b0 Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 04:51:00 +0900 Subject: [PATCH 04/29] add mdm --- mdm/CMakeLists.txt | 26 ++++++++++++++++++++++++++ mdm/main.c | 1 + 2 files changed, 27 insertions(+) create mode 100644 mdm/CMakeLists.txt create mode 100644 mdm/main.c diff --git a/mdm/CMakeLists.txt b/mdm/CMakeLists.txt new file mode 100644 index 0000000..b3776a4 --- /dev/null +++ b/mdm/CMakeLists.txt @@ -0,0 +1,26 @@ +project( + mdm +) + +file( + GLOB + mdm_SRC + *.c +) + +add_executable( + mdm + ${mdm_SRC} +) + +target_link_libraries( + mdm + PRIVATE + MDELib +) + +include(GNUInstallDirs) +install( + TARGETS mdm + RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} +) diff --git a/mdm/main.c b/mdm/main.c new file mode 100644 index 0000000..979a1cd --- /dev/null +++ b/mdm/main.c @@ -0,0 +1 @@ +int main(){} From b2572fa7f1691658f93e11b7cf39e9127e3aab68 Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 04:51:13 +0900 Subject: [PATCH 05/29] oops --- CMakeLists.txt | 1 + milkwm/CMakeLists.txt | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6137faa..2b52f53 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,4 +3,5 @@ project( mdebase ) +add_subdirectory(mdm) add_subdirectory(milkwm) diff --git a/milkwm/CMakeLists.txt b/milkwm/CMakeLists.txt index fd40150..f5278b8 100644 --- a/milkwm/CMakeLists.txt +++ b/milkwm/CMakeLists.txt @@ -13,6 +13,12 @@ add_executable( ${milkwm_SRC} ) +target_link_libraries( + mdm + PRIVATE + MDELib +) + include(GNUInstallDirs) install( TARGETS milkwm From 45a24f90b99b2264e2a8fd9356d6ee11c3d7d83e Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 08:26:47 +0900 Subject: [PATCH 06/29] mdm --- mdm/CMakeLists.txt | 17 +++++++++++- mdm/login.c | 29 ++++++++++++++++++++ mdm/main.c | 30 ++++++++++++++++++++- mdm/mdm.h | 11 ++++++++ mdm/xserver.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 mdm/login.c create mode 100644 mdm/mdm.h create mode 100644 mdm/xserver.c diff --git a/mdm/CMakeLists.txt b/mdm/CMakeLists.txt index b3776a4..2610e02 100644 --- a/mdm/CMakeLists.txt +++ b/mdm/CMakeLists.txt @@ -2,6 +2,9 @@ project( mdm ) +find_package(PkgConfig) +pkg_check_modules(X11 REQUIRED x11) + file( GLOB mdm_SRC @@ -13,10 +16,22 @@ add_executable( ${mdm_SRC} ) +target_include_directories( + mdm + PRIVATE + ${X11_INCLUDE_DIRS} +) + +target_link_directories( + mdm + PRIVATE + ${X11_LIBRARY_DIRS} +) + target_link_libraries( mdm PRIVATE - MDELib + ${X11_LIBRARIES} MDELib Mw ) include(GNUInstallDirs) diff --git a/mdm/login.c b/mdm/login.c new file mode 100644 index 0000000..c09a2fd --- /dev/null +++ b/mdm/login.c @@ -0,0 +1,29 @@ +#include "mdm.h" + +#include + +void make_login_window(void){ + MwWidget w, pic, userlabel, usercombo, passlabel, passentry; + + MwLibraryInit(); + + w = MwVaCreateWidget(MwWindowClass, "login", NULL, (x_width() - 366) / 2, (x_height() - 183) / 2, 366, 183, + MwNhasBorder, 1, + MwNinverted, 0, + NULL); + pic = MwVaCreateWidget(MwImageClass, "image", w, 10, 10, 80, 137, + MwNhasBorder, 1, + NULL); + userlabel = MwVaCreateWidget(MwLabelClass, "userlabel", w, 95, 10, MwTextWidth(w, "User:"), 16, + MwNtext, "User:", + NULL); + usercombo = MwCreateWidget(MwComboBoxClass, "usercombo", w, 95, 10+16+5, 265, 18); + passlabel = MwVaCreateWidget(MwLabelClass, "passlabel", w, 95, 10+16+5+18+5, MwTextWidth(w, "Password:"), 16, + MwNtext, "Password:", + NULL); + passentry = MwVaCreateWidget(MwEntryClass, "passentry", w, 95, 10+16+5+18+5+16+5, 265, 18, + MwNhideInput, 1, + NULL); + + MwLoop(w); +} diff --git a/mdm/main.c b/mdm/main.c index 979a1cd..0231884 100644 --- a/mdm/main.c +++ b/mdm/main.c @@ -1 +1,29 @@ -int main(){} +#include "mdm.h" + +#include +#include + +int is_launch_x = 1; + +int main(int argc, char** argv){ + int i, st; + + 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{ + fprintf(stderr, "%s: bad option: %s\n", argv[0], argv[i]); + return 1; + } + } + } + + if(is_launch_x){ + if((st = launch_x()) != 0) return st; + } + + init_x(); + + make_login_window(); +} diff --git a/mdm/mdm.h b/mdm/mdm.h new file mode 100644 index 0000000..60b27ed --- /dev/null +++ b/mdm/mdm.h @@ -0,0 +1,11 @@ +#ifndef __MDM_H__ +#define __MDM_H__ + +int launch_x(void); +int x_width(void); +int x_height(void); +void init_x(void); + +void make_login_window(void); + +#endif diff --git a/mdm/xserver.c b/mdm/xserver.c new file mode 100644 index 0000000..1bef1b0 --- /dev/null +++ b/mdm/xserver.c @@ -0,0 +1,67 @@ +#include "mdm.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static pid_t x_server; +static int got_usr1; +static Display* display; + +static void catch_usr1(int sig){ + got_usr1 = 1; +} + +int launch_x(void){ + got_usr1 = 0; + + signal(SIGUSR1, catch_usr1); + + if((x_server = fork()) == 0){ + signal(SIGUSR1, SIG_IGN); + execlp("X", "X", ":0", NULL); + _exit(-1); + }else{ + while(!got_usr1){ + if(waitpid(x_server, NULL, WNOHANG) != 0){ + fprintf(stderr, "server died. what happened?\n"); + return 1; + } + usleep(1000); + } + } + + setenv("DISPLAY", ":0", 1); + + return 0; +} + +void init_x(void){ + XColor bg; + + display = XOpenDisplay(NULL); + + bg.red = 0x1c * 256; + bg.green = 0x45 * 256; + bg.blue = 0x73 * 256; + XAllocColor(display, DefaultColormap(display, DefaultScreen(display)), &bg); + + XSetWindowBackground(display, DefaultRootWindow(display), bg.pixel); + XClearWindow(display, DefaultRootWindow(display)); + XFlush(display); +} + +int x_width(void){ + return DisplayWidth(display, DefaultScreen(display)); +} + +int x_height(void){ + return DisplayHeight(display, DefaultScreen(display)); +} From e3ea52114dec53a2f0029f23f4d72afdf112886d Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 10:03:10 +0900 Subject: [PATCH 07/29] at least window --- CMakeLists.txt | 2 ++ include/config.h.in | 6 ++++++ mdm/login.c | 35 +++++++++++++++++++++++++++-------- 3 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 include/config.h.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b52f53..0a9beb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,3 +5,5 @@ project( add_subdirectory(mdm) add_subdirectory(milkwm) + +configure_file(include/config.h.in config.h) diff --git a/include/config.h.in b/include/config.h.in new file mode 100644 index 0000000..5e7ea24 --- /dev/null +++ b/include/config.h.in @@ -0,0 +1,6 @@ +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +#define PREFIX "@CMAKE_INSTALL_PREFIX@" + +#endif diff --git a/mdm/login.c b/mdm/login.c index c09a2fd..c2fde99 100644 --- a/mdm/login.c +++ b/mdm/login.c @@ -3,27 +3,46 @@ #include void make_login_window(void){ - MwWidget w, pic, userlabel, usercombo, passlabel, passentry; + MwWidget window, pic, userlabel, usercombo, passlabel, passentry, mainsep, sesslabel, sesscombo, fieldsep, shutdown, reboot, ok; MwLibraryInit(); - w = MwVaCreateWidget(MwWindowClass, "login", NULL, (x_width() - 366) / 2, (x_height() - 183) / 2, 366, 183, + window = MwVaCreateWidget(MwWindowClass, "login", NULL, (x_width() - 366) / 2, (x_height() - 183) / 2, 366, 183, MwNhasBorder, 1, MwNinverted, 0, NULL); - pic = MwVaCreateWidget(MwImageClass, "image", w, 10, 10, 80, 137, + pic = MwVaCreateWidget(MwImageClass, "image", window, 10, 10, 80, 137, MwNhasBorder, 1, NULL); - userlabel = MwVaCreateWidget(MwLabelClass, "userlabel", w, 95, 10, MwTextWidth(w, "User:"), 16, + userlabel = MwVaCreateWidget(MwLabelClass, "userlabel", window, 95, 10, MwTextWidth(window, "User:"), 16, MwNtext, "User:", NULL); - usercombo = MwCreateWidget(MwComboBoxClass, "usercombo", w, 95, 10+16+5, 265, 18); - passlabel = MwVaCreateWidget(MwLabelClass, "passlabel", w, 95, 10+16+5+18+5, MwTextWidth(w, "Password:"), 16, + 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", w, 95, 10+16+5+18+5+16+5, 265, 18, + 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, (366 - 362) / 2, 10+137, 362, 10, + MwNorientation, MwHORIZONTAL, + NULL); + shutdown = MwVaCreateWidget(MwButtonClass, "shutdown", window, 10, 10+137+10, 80, 18, + MwNtext, "Shutdown", + 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); - MwLoop(w); + MwLoop(window); } From 0685e3556ba9751027b852f68a1697ce2d2568ab Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 11:25:23 +0900 Subject: [PATCH 08/29] things --- CMakeLists.txt | 25 +++++++++++++++++++++++-- include/config.h.in | 2 ++ mdm/CMakeLists.txt | 27 ++++++++++++++------------- mdm/main.c | 9 +++++++++ mdm/mdm.png | Bin 0 -> 5033 bytes mdm/mdmrc.in | 2 ++ 6 files changed, 50 insertions(+), 15 deletions(-) create mode 100644 mdm/mdm.png create mode 100644 mdm/mdmrc.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a9beb2..b35678c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,28 @@ project( mdebase ) +macro(PKGCONF_ADD dest name) + pkg_check_modules(${name} REQUIRED ${name}) + target_include_directories( + ${dest} + PRIVATE + ${${name}_INCLUDE_DIRS} + ) + target_link_directories( + ${dest} + PRIVATE + ${${name}_LIBRARY_DIRS} + ) + target_link_libraries( + ${dest} + PRIVATE + ${${name}_LIBRARIES} + ) +endmacro() + +include(GNUInstallDirs) +configure_file(include/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + add_subdirectory(mdm) add_subdirectory(milkwm) - -configure_file(include/config.h.in config.h) diff --git a/include/config.h.in b/include/config.h.in index 5e7ea24..f041cba 100644 --- a/include/config.h.in +++ b/include/config.h.in @@ -2,5 +2,7 @@ #define __CONFIG_H__ #define PREFIX "@CMAKE_INSTALL_PREFIX@" +#define CONFDIR "@CMAKE_INSTALL_FULL_SYSCONFDIR@" +#define DATADIR "@CMAKE_INSTALL_FULL_DATAROOTDIR@" #endif diff --git a/mdm/CMakeLists.txt b/mdm/CMakeLists.txt index 2610e02..fdac35f 100644 --- a/mdm/CMakeLists.txt +++ b/mdm/CMakeLists.txt @@ -3,7 +3,6 @@ project( ) find_package(PkgConfig) -pkg_check_modules(X11 REQUIRED x11) file( GLOB @@ -16,26 +15,28 @@ add_executable( ${mdm_SRC} ) -target_include_directories( - mdm - PRIVATE - ${X11_INCLUDE_DIRS} -) - -target_link_directories( - mdm - PRIVATE - ${X11_LIBRARY_DIRS} -) +PKGCONF_ADD(mdm x11) +PKGCONF_ADD(mdm inih) target_link_libraries( mdm PRIVATE - ${X11_LIBRARIES} MDELib Mw + MDELib Mw ) + include(GNUInstallDirs) +configure_file(mdmrc.in mdmrc) + install( TARGETS mdm RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} ) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/mdmrc + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/examples/mdm +) +install( + FILES mdm.png + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/mdm +) diff --git a/mdm/main.c b/mdm/main.c index 0231884..be1dfac 100644 --- a/mdm/main.c +++ b/mdm/main.c @@ -1,7 +1,13 @@ #include "mdm.h" +#include "config.h" + +#include + #include #include +#include +#include int is_launch_x = 1; @@ -19,6 +25,9 @@ int main(int argc, char** argv){ } } + 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"); + if(is_launch_x){ if((st = launch_x()) != 0) return st; } diff --git a/mdm/mdm.png b/mdm/mdm.png new file mode 100644 index 0000000000000000000000000000000000000000..a190f66782c7309b591fc161a2d8a9f5fbf992fb GIT binary patch literal 5033 zcmV;a6ISerP) zCc{@4cCJk;UtKs0cRXSxKCO>Ubznvg0VLu)FRb_Ztk1Ri04r=4Yu}~B!n#Ssp{xmXg&*cAGu#tqTi`ujPE~}6T!(ze4F>RXq$tOemAio#_ zk{U7lcfb4XOv~6iCpt_fwY8PBv=FORylP4m9Z%#*I<{@Y)@d@0HQ!B-8&8Ahzf7N= z-q`r;Fcl>cCh1tUisa`Jx2-oeHnp|2wYIjFmy}$v+ZQVeFDy-weXw6dnB( z&$Ra+Idb^<7oI0tI*w8^b0kR8LXD6gO;3wCuGwJ`QA~Y(nZCa_)bJ5PH0Kd2;6-76 zVoQ(30vUP_e-OQvp@Seviv_)OCMUxy;%H30)$rM8l1uatd{;H!?Pxr$GMD2P~q_z1}d6d*;UtgnZ<5uHI4LeHvlF_LHB!*VWbV z@yGt0hs6S2#oyn2UOnz|r^lHXLu7j#23#UqrznpeI*Rbk4w0$wE6nB@sS6h#Q`H9F zcY#}`+s4ypy1K0W_B=s!*Y8aB^{uFtl=Lx~+R(7t(lV{Pg|#A65qUoN;1c^Uf7$B$ zE=Y8s+uap^EN^Xx=%BRx)Py@GL~{^87s9*r42caD zS3R$~nx8SoPj%f*?|V_Jn>`eU`9=VgUVUe`=p^&xWDZ?)red|2{g(8ky35IB%ZRW96ZTu>I}j3#8GCr~siUot^Akz9Mjx;X#sj z4t$8&Vm)~h-U#l5UjU_DwrQP*(jw#4lJc(4>yV?dsXGzw%x*a0X_GB2d_Og1GPVs? z>$tbJi%L6d$rCXfHgKh7i4HD6@F4D)N%1~3vx|+>^~_PLn?2Q4aIlLOFLFr$je^p` zmmt?Gf8-G+OqS@BDhg_Ukl_x7`!EW#Wq30?`Pm+w)2bxe_{k?wTFw(ni}c{^q->Vx zpuM~EJl@Dgn=PbgN6qERBzYV+X_m$%0HqcABRwcxnnTALHP=F2l;|x5jbQ2P@Ioeu z5XU6OI`^sF?mFSAwD3&%`T3~Dh$eiba?n+=A`_DyFZ;MP+FAN?|%uP~D>Vw7OBVW_4Cp5=Gg(H%U4*`Y%@p&w+M(vbLQ> zoR@TVWRh@xum6O-M@6(J}5|kdf(p z!)jG_?BGX8{h+H0`}@6JC3_bLIV`g~x-=m&T@D>OBqaxX%qzqhw!6{;=<3nxdZJfP zz_!K1pFWa!Wqf;Nx*R%m2A93f$s6iRFWF2cRLV?O7rUL)zy&QWJo`qxX&8+MZ5eE5 z!-b9{c?K&-5fPfwQoYqYldP*2i!0sJ03UWNVe5phv`BV56zE8j3jv*zqKF8pw1g~F z6iHX@&pd;=MA4>wh&GaPl3`yHG|B^R&v_yZM@|hRVSic>*`@UxC7o)7-}D2 zE`&vq2Hz>z>t4%aXyuikr_E=z?s|7uQE?IU`N~h^LImA?KH*<2hMBt6p4DEDQ`pi2 z?y&51zg#K9ZmqK#euqau?p>4ge*8Vu{qk77kP!Gaj+f16gFB73JNjO+G1NVHi7BQ^5 zu5rgLyyeR`ZK%9iPl)CY!Jm&FtyLZe)y;+tiS{JiGX#=6%F%I<**P(DW;?E4RlVl@ zgTd$b1~7Z`>KRj(rx5o$Y;Gtkyk1lNVXbi)wUKwLsKGnxTxa8x=H}ivY3;Hlwgrns zA@8v7hDh7HI&`g6zXv>n#XH-&_2)~|(`g@Ge!0|Y)tzkg1OM5aVV1Kq>zvqv()9FE zgF2hUy}~XakhXBXy6jSQ89A`JaqXJ6wRk(+s^^^h{haSy{I4 zea~j=7eDp&r?x-wM0Q+Uk@xj}Khv(JjdKt`pC_Gaa|$puaW;mT!C4#Mo4wd=(SZ}x12jzWn4BOPmdr)CuBEoW|MhvaRmi$&YO4d z%2g|?_UxHjTU|ty>JX>?0~#a^BnXvmz#y-DaV7ZsBUHk*iY*zLxzBw%bIR9qMJ4y> z1WfTp#UID)*zvP3zIg59kI;fZx6xogYML3=kbc1O3V}p?&oIdI7cRme)q8@vKh&iB z8?rxP9~UOPZu5#2@q25?z^jwg73HEflV?+uH&nx7hl2&WpdRQ2U%^8}njzg{ABgyf z7BC9tSAzsK2hZf1rQE$QlK5g*i%w3JsU>R&bb}mC3#hBqAZ@w)5C&O(teo~KgXnlj z-;gvGm@|H^qr+)C=z3X0I2;;KS0#hw^GgQ(B*!O^;v>uBhlro7+;?7dwG0W#=fCqa zb^57&<^Y3K)F1^kRFpG=WTR4p#4M6ycjtMLT#DE8Y>Zv9XbaZjeM%d#S}_GKmgs zP$VI`W4*brC#~~7l`*gKQlOrp)Z!^C43b62Pz_QS%)!Fzs+%QQFh;EGj1(()+ z?*9%_ghYkS3^EL4D>>4y+i*A36(@;nVm~yJ2z~F|ctTfnA|}kQ>j`u#$sk)>TcKdV zrvVbA;u}c?(P|fF-ocV|IhELZh&7+xoX%%B;Cka~(M zkQj7Gh9No;u^USQUK2rsl-`#v21pJ@bRyJAXZ&9v86=_a-er(sJWhIF;tkb_%7EkX z4&{S^!SbH}3nYVtc5#D@h}VJgOBQZKk{RlK{eT9KKpl;sLDH#*1fBSS8xkRTig1G7 zm3cAXHB`YI9tH__Pe?dPFNi_SR?tY>oj>-fHD1}(8 z5usiaRU^VbehC;nybaRTr;KnOx54?e)B@$Ns-jEY@z%Jnj-(`EkO8leYDm_n@C=ec zGDs*tn@tqonySR6;Dfy|w$dT8xqy9=400gJ4Ba4IX@CGWfKuDn%MyL=t6h=|kEe7v zIWGW7Z-Zn`B8)N*VPT= zhD*G@36c%}6T(Wxm)|BImgx-s>l!3m!~)f#WBB8VG@y3*CSB&7JuUm%_rJM9+WkBf z^q>W>L#m~olTQT&Z_?=RR@B&Mh;9OGoSqf~on}idh|}I+ll*|Hx!A!NDF${9i)uV@sCUVx?x%#1MNr#YLl&Ru%woOE>OB~k?&)>?Uh&9Duk4{z42e|bBw#UH}>E8^#RA` zbb%K)J&i64e$>91idv-$l-ML(xv^|fLN4-ntFs>Z$=39UV5_`1Lpp9UQ5p3spN4Hj z@8JMY(V^@8%6v4P?yBrpRDI%DZ_|hc09Er}05^s9Mb9&#!;Q=LPy)S~=1w>nRg(3-L>@jnohrMJa(b6=& zf9eeMk(Q@-j+Px&z-;bB;hzq_;96djJv(Ra+&N~m8TZLtu!yi<*#rmZCsI?WH=+ge zrNr${!O*{dHfm2Y-&Ivpf6c?v#Qh6b#(Z#~Cudqa8?+_k2EHFKn~QlGk5}wX#L-Mn zc2w6E4%F=640FIM&d<*$?o~+Bx?7sugIvrnAYTJFWDd|cPLDA}+(#l2FalAMZ1HZ4 zv$Cgej>wGz9<{ou=Uh|m%-i;MU#%WB?p`Km5?nS1o=ivCVg=_VVms{STueFdEZiw_ zCfa*%Y;5F#6h1;6(Qw7XCHlV98;Z@=4?AAJenVZ`6z3hk5J{$|^yJJ)U?hpAnTCGq zpP<#mE$W1mw*~VUT}YfA9ZrW(b%Tk+vj}HdE~5uW8hqj6FBt&C-@FQ%Q-tQ5~kj&_HS(PypMBZQ>(#0h}q-}_=D7xD+k(AAU*J5 z+ms34o@pNgfB4{}7Yi%CmgG#dY)&368w5CK;{GWGUyCDA3bpujNpj1U=dfMXq9)(Y zCZ=iUQMNaIu(@m@EfcmCb|b)q^ccLRoAy*^U6rFwn`T0w zZ)R-iHu&5nJgDb8UB4&S|M{64U(@Kfg{Jy@omwqI@xa{`_O>5FID>yYXG^qIV*SM) zggsZEhmQeug@Z-RNcyV3ErpU{d5`DCD!-CwtLMBG+Yg|wgjD%D%hFURGfPluDvQrM zPC8(jK?douT;7#KjQ(t2+_HAK;+eCu4C#A9^FtRlI Date: Sun, 16 Nov 2025 11:27:50 +0900 Subject: [PATCH 09/29] add config.c --- mdm/config.c | 1 + mdm/login.c | 2 +- mdm/main.c | 2 +- mdm/mdm.h | 4 +++- 4 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 mdm/config.c diff --git a/mdm/config.c b/mdm/config.c new file mode 100644 index 0000000..21782b0 --- /dev/null +++ b/mdm/config.c @@ -0,0 +1 @@ +#include "mdm.h" diff --git a/mdm/login.c b/mdm/login.c index c2fde99..f8eb3d1 100644 --- a/mdm/login.c +++ b/mdm/login.c @@ -2,7 +2,7 @@ #include -void make_login_window(void){ +void login_window(void){ MwWidget window, pic, userlabel, usercombo, passlabel, passentry, mainsep, sesslabel, sesscombo, fieldsep, shutdown, reboot, ok; MwLibraryInit(); diff --git a/mdm/main.c b/mdm/main.c index be1dfac..62b0e53 100644 --- a/mdm/main.c +++ b/mdm/main.c @@ -34,5 +34,5 @@ int main(int argc, char** argv){ init_x(); - make_login_window(); + login_window(); } diff --git a/mdm/mdm.h b/mdm/mdm.h index 60b27ed..72d543a 100644 --- a/mdm/mdm.h +++ b/mdm/mdm.h @@ -1,11 +1,13 @@ #ifndef __MDM_H__ #define __MDM_H__ +/* xserver.c */ int launch_x(void); int x_width(void); int x_height(void); void init_x(void); -void make_login_window(void); +/* login.c */ +void login_window(void); #endif From 2f6a6b764f71902f371cf27b5d08b258e1046719 Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 12:09:04 +0900 Subject: [PATCH 10/29] can load image now --- mdm/config.c | 16 ++++++++++++++++ mdm/login.c | 4 ++++ mdm/main.c | 11 ++--------- mdm/mdm.h | 18 ++++++++++++++++++ mdm/mdmrc.in | 2 +- mdm/pam | 5 +++++ mdm/xserver.c | 9 --------- 7 files changed, 46 insertions(+), 19 deletions(-) create mode 100644 mdm/pam diff --git a/mdm/config.c b/mdm/config.c index 21782b0..eed9d22 100644 --- a/mdm/config.c +++ b/mdm/config.c @@ -1 +1,17 @@ #include "mdm.h" + +#include + +char* config_picture = NULL; + +static int dumper(void* user, const char* section, const char* name, const char* value){ + 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); +} diff --git a/mdm/login.c b/mdm/login.c index f8eb3d1..1162123 100644 --- a/mdm/login.c +++ b/mdm/login.c @@ -4,6 +4,7 @@ void login_window(void){ MwWidget window, pic, userlabel, usercombo, passlabel, passentry, mainsep, sesslabel, sesscombo, fieldsep, shutdown, reboot, ok; + MwLLPixmap p; MwLibraryInit(); @@ -11,6 +12,7 @@ void login_window(void){ MwNhasBorder, 1, MwNinverted, 0, NULL); + p = config_picture == NULL ? NULL : MwLoadImage(window, config_picture); pic = MwVaCreateWidget(MwImageClass, "image", window, 10, 10, 80, 137, MwNhasBorder, 1, NULL); @@ -44,5 +46,7 @@ void login_window(void){ MwNtext, "OK", NULL); + if(p != NULL) MwVaApply(pic, MwNpixmap, p, NULL); + MwLoop(window); } diff --git a/mdm/main.c b/mdm/main.c index 62b0e53..3aeeeac 100644 --- a/mdm/main.c +++ b/mdm/main.c @@ -1,14 +1,5 @@ #include "mdm.h" -#include "config.h" - -#include - -#include -#include -#include -#include - int is_launch_x = 1; int main(int argc, char** argv){ @@ -28,6 +19,8 @@ int main(int argc, char** argv){ 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(is_launch_x){ if((st = launch_x()) != 0) return st; } diff --git a/mdm/mdm.h b/mdm/mdm.h index 72d543a..74b7771 100644 --- a/mdm/mdm.h +++ b/mdm/mdm.h @@ -1,6 +1,19 @@ #ifndef __MDM_H__ #define __MDM_H__ +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + /* xserver.c */ int launch_x(void); int x_width(void); @@ -10,4 +23,9 @@ void init_x(void); /* login.c */ void login_window(void); +/* config.c */ +extern char* config_picture; + +void parse_config(void); + #endif diff --git a/mdm/mdmrc.in b/mdm/mdmrc.in index 17deeb5..dd1e3fe 100644 --- a/mdm/mdmrc.in +++ b/mdm/mdmrc.in @@ -1,2 +1,2 @@ -[MDM Entry] +[MDM Config] Picture=@CMAKE_INSTALL_FULL_DATAROOTDIR@/mdm/mdm.png diff --git a/mdm/pam b/mdm/pam new file mode 100644 index 0000000..e21bb83 --- /dev/null +++ b/mdm/pam @@ -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 diff --git a/mdm/xserver.c b/mdm/xserver.c index 1bef1b0..62621d6 100644 --- a/mdm/xserver.c +++ b/mdm/xserver.c @@ -2,15 +2,6 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include - static pid_t x_server; static int got_usr1; static Display* display; From 1f4df702616b14edef77da058ba4de12c333bcec Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 12:10:33 +0900 Subject: [PATCH 11/29] pam --- mdm/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mdm/CMakeLists.txt b/mdm/CMakeLists.txt index fdac35f..2e2ec5d 100644 --- a/mdm/CMakeLists.txt +++ b/mdm/CMakeLists.txt @@ -32,6 +32,11 @@ install( TARGETS mdm RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} ) +install( + FILES pam + RENAME mdm + DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/pam.d +) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/mdmrc DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/examples/mdm From db62db0a8734b6fe2ec103366547ddca6d205293 Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 12:12:06 +0900 Subject: [PATCH 12/29] pam --- mdm/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mdm/CMakeLists.txt b/mdm/CMakeLists.txt index 2e2ec5d..73cbc71 100644 --- a/mdm/CMakeLists.txt +++ b/mdm/CMakeLists.txt @@ -35,7 +35,7 @@ install( install( FILES pam RENAME mdm - DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/pam.d + DESTINATION /etc/pam.d ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/mdmrc From 3ca1b0ed6a6fb2af489ada81ded9a3e70e65be33 Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 12:57:23 +0900 Subject: [PATCH 13/29] lists user --- mdm/CMakeLists.txt | 2 +- mdm/login.c | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/mdm/CMakeLists.txt b/mdm/CMakeLists.txt index 73cbc71..fc5c073 100644 --- a/mdm/CMakeLists.txt +++ b/mdm/CMakeLists.txt @@ -21,7 +21,7 @@ PKGCONF_ADD(mdm inih) target_link_libraries( mdm PRIVATE - MDELib Mw + pam MDELib Mw ) diff --git a/mdm/login.c b/mdm/login.c index 1162123..4011db1 100644 --- a/mdm/login.c +++ b/mdm/login.c @@ -2,6 +2,10 @@ #include +static void add_user(const char* name, void* user){ + MwComboBoxAdd(user, -1, name); +} + void login_window(void){ MwWidget window, pic, userlabel, usercombo, passlabel, passentry, mainsep, sesslabel, sesscombo, fieldsep, shutdown, reboot, ok; MwLLPixmap p; @@ -48,5 +52,7 @@ void login_window(void){ if(p != NULL) MwVaApply(pic, MwNpixmap, p, NULL); + MDEListUsers(add_user, usercombo); + MwLoop(window); } From 3b55fb0c3828e59303f0a1790c6a0542618b1bd4 Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 18:02:03 +0900 Subject: [PATCH 14/29] so far so good --- mdm/CMakeLists.txt | 2 +- mdm/config.c | 2 + mdm/login.c | 24 +- mdm/main.c | 2 +- mdm/mdm.h | 13 +- mdm/stb_ds.c | 2 + mdm/stb_ds.h | 1895 ++++++++++++++++++++++++++++++++++++++++++++ mdm/xserver.c | 99 ++- 8 files changed, 2019 insertions(+), 20 deletions(-) create mode 100644 mdm/stb_ds.c create mode 100644 mdm/stb_ds.h diff --git a/mdm/CMakeLists.txt b/mdm/CMakeLists.txt index fc5c073..88b61d9 100644 --- a/mdm/CMakeLists.txt +++ b/mdm/CMakeLists.txt @@ -21,7 +21,7 @@ PKGCONF_ADD(mdm inih) target_link_libraries( mdm PRIVATE - pam MDELib Mw + pam pthread MDELib Mw ) diff --git a/mdm/config.c b/mdm/config.c index eed9d22..78fcbfa 100644 --- a/mdm/config.c +++ b/mdm/config.c @@ -5,6 +5,8 @@ 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); diff --git a/mdm/login.c b/mdm/login.c index 4011db1..2092213 100644 --- a/mdm/login.c +++ b/mdm/login.c @@ -1,6 +1,6 @@ #include "mdm.h" -#include +MwWidget root; static void add_user(const char* name, void* user){ MwComboBoxAdd(user, -1, name); @@ -11,10 +11,11 @@ void login_window(void){ MwLLPixmap p; MwLibraryInit(); + + root = MwCreateWidget(NULL, "root", NULL, 0, 0, 0, 0); - window = MwVaCreateWidget(MwWindowClass, "login", NULL, (x_width() - 366) / 2, (x_height() - 183) / 2, 366, 183, - MwNhasBorder, 1, - MwNinverted, 0, + window = MwVaCreateWidget(MwWindowClass, "login", root, (x_width() - 366) / 2, (x_height() - 183) / 2, 366, 183, + MwNtitle, "login", NULL); p = config_picture == NULL ? NULL : MwLoadImage(window, config_picture); pic = MwVaCreateWidget(MwImageClass, "image", window, 10, 10, 80, 137, @@ -41,7 +42,7 @@ void login_window(void){ MwNorientation, MwHORIZONTAL, NULL); shutdown = MwVaCreateWidget(MwButtonClass, "shutdown", window, 10, 10+137+10, 80, 18, - MwNtext, "Shutdown", + MwNtext, "Shut Down", NULL); reboot = MwVaCreateWidget(MwButtonClass, "reboot", window, 10+80+5, 10+137+10, 70, 18, MwNtext, "Reboot", @@ -54,5 +55,16 @@ void login_window(void){ MDEListUsers(add_user, usercombo); - MwLoop(window); + 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); + } } diff --git a/mdm/main.c b/mdm/main.c index 3aeeeac..bb61394 100644 --- a/mdm/main.c +++ b/mdm/main.c @@ -25,7 +25,7 @@ int main(int argc, char** argv){ if((st = launch_x()) != 0) return st; } - init_x(); + if((st = init_x()) != 0) return st; login_window(); } diff --git a/mdm/mdm.h b/mdm/mdm.h index 74b7771..fe1d9db 100644 --- a/mdm/mdm.h +++ b/mdm/mdm.h @@ -4,6 +4,9 @@ #include "config.h" #include +#include + +#include #include #include @@ -13,14 +16,22 @@ #include #include #include +#include /* 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); -void init_x(void); +int init_x(void); +void x_loop(void); /* login.c */ +extern MwWidget root; + void login_window(void); /* config.c */ diff --git a/mdm/stb_ds.c b/mdm/stb_ds.c new file mode 100644 index 0000000..9df32fd --- /dev/null +++ b/mdm/stb_ds.c @@ -0,0 +1,2 @@ +#define STB_DS_IMPLEMENTATION +#include "stb_ds.h" diff --git a/mdm/stb_ds.h b/mdm/stb_ds.h new file mode 100644 index 0000000..e84c82d --- /dev/null +++ b/mdm/stb_ds.h @@ -0,0 +1,1895 @@ +/* stb_ds.h - v0.67 - public domain data structures - Sean Barrett 2019 + + This is a single-header-file library that provides easy-to-use + dynamic arrays and hash tables for C (also works in C++). + + For a gentle introduction: + http://nothings.org/stb_ds + + To use this library, do this in *one* C or C++ file: + #define STB_DS_IMPLEMENTATION + #include "stb_ds.h" + +TABLE OF CONTENTS + + Table of Contents + Compile-time options + License + Documentation + Notes + Notes - Dynamic arrays + Notes - Hash maps + Credits + +COMPILE-TIME OPTIONS + + #define STBDS_NO_SHORT_NAMES + + This flag needs to be set globally. + + By default stb_ds exposes shorter function names that are not qualified + with the "stbds_" prefix. If these names conflict with the names in your + code, define this flag. + + #define STBDS_SIPHASH_2_4 + + This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for + 4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force + stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes + hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on + 64-byte keys, and 10% slower on 256-byte keys on my test computer. + + #define STBDS_REALLOC(context,ptr,size) better_realloc + #define STBDS_FREE(context,ptr) better_free + + These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds uses stdlib realloc() and free() for memory management. You can + substitute your own functions instead by defining these symbols. You must either + define both, or neither. Note that at the moment, 'context' will always be NULL. + @TODO add an array/hash initialization function that takes a memory context pointer. + + #define STBDS_UNIT_TESTS + + Defines a function stbds_unit_tests() that checks the functioning of the data structures. + + Note that on older versions of gcc (e.g. 5.x.x) you may need to build with '-std=c++0x' + (or equivalentally '-std=c++11') when using anonymous structures as seen on the web + page or in STBDS_UNIT_TESTS. + +LICENSE + + Placed in the public domain and also MIT licensed. + See end of file for detailed license information. + +DOCUMENTATION + + Dynamic Arrays + + Non-function interface: + + Declare an empty dynamic array of type T + T* foo = NULL; + + Access the i'th item of a dynamic array 'foo' of type T, T* foo: + foo[i] + + Functions (actually macros) + + arrfree: + void arrfree(T*); + Frees the array. + + arrlen: + ptrdiff_t arrlen(T*); + Returns the number of elements in the array. + + arrlenu: + size_t arrlenu(T*); + Returns the number of elements in the array as an unsigned type. + + arrpop: + T arrpop(T* a) + Removes the final element of the array and returns it. + + arrput: + T arrput(T* a, T b); + Appends the item b to the end of array a. Returns b. + + arrins: + T arrins(T* a, int p, T b); + Inserts the item b into the middle of array a, into a[p], + moving the rest of the array over. Returns b. + + arrinsn: + void arrinsn(T* a, int p, int n); + Inserts n uninitialized items into array a starting at a[p], + moving the rest of the array over. + + arraddnptr: + T* arraddnptr(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns a pointer to the first uninitialized item added. + + arraddnindex: + size_t arraddnindex(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns the index of the first uninitialized item added. + + arrdel: + void arrdel(T* a, int p); + Deletes the element at a[p], moving the rest of the array over. + + arrdeln: + void arrdeln(T* a, int p, int n); + Deletes n elements starting at a[p], moving the rest of the array over. + + arrdelswap: + void arrdelswap(T* a, int p); + Deletes the element at a[p], replacing it with the element from + the end of the array. O(1) performance. + + arrsetlen: + void arrsetlen(T* a, int n); + Changes the length of the array to n. Allocates uninitialized + slots at the end if necessary. + + arrsetcap: + size_t arrsetcap(T* a, int n); + Sets the length of allocated storage to at least n. It will not + change the length of the array. + + arrcap: + size_t arrcap(T* a); + Returns the number of total elements the array can contain without + needing to be reallocated. + + Hash maps & String hash maps + + Given T is a structure type: struct { TK key; TV value; }. Note that some + functions do not require TV value and can have other fields. For string + hash maps, TK must be 'char *'. + + Special interface: + + stbds_rand_seed: + void stbds_rand_seed(size_t seed); + For security against adversarially chosen data, you should seed the + library with a strong random number. Or at least seed it with time(). + + stbds_hash_string: + size_t stbds_hash_string(char *str, size_t seed); + Returns a hash value for a string. + + stbds_hash_bytes: + size_t stbds_hash_bytes(void *p, size_t len, size_t seed); + These functions hash an arbitrary number of bytes. The function + uses a custom hash for 4- and 8-byte data, and a weakened version + of SipHash for everything else. On 64-bit platforms you can get + specification-compliant SipHash-2-4 on all data by defining + STBDS_SIPHASH_2_4, at a significant cost in speed. + + Non-function interface: + + Declare an empty hash map of type T + T* foo = NULL; + + Access the i'th entry in a hash table T* foo: + foo[i] + + Function interface (actually macros): + + hmfree + shfree + void hmfree(T*); + void shfree(T*); + Frees the hashmap and sets the pointer to NULL. + + hmlen + shlen + ptrdiff_t hmlen(T*) + ptrdiff_t shlen(T*) + Returns the number of elements in the hashmap. + + hmlenu + shlenu + size_t hmlenu(T*) + size_t shlenu(T*) + Returns the number of elements in the hashmap. + + hmgeti + shgeti + hmgeti_ts + ptrdiff_t hmgeti(T*, TK key) + ptrdiff_t shgeti(T*, char* key) + ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar) + Returns the index in the hashmap which has the key 'key', or -1 + if the key is not present. + + hmget + hmget_ts + shget + TV hmget(T*, TK key) + TV shget(T*, char* key) + TV hmget_ts(T*, TK key, ptrdiff_t tempvar) + Returns the value corresponding to 'key' in the hashmap. + The structure must have a 'value' field + + hmgets + shgets + T hmgets(T*, TK key) + T shgets(T*, char* key) + Returns the structure corresponding to 'key' in the hashmap. + + hmgetp + shgetp + hmgetp_ts + hmgetp_null + shgetp_null + T* hmgetp(T*, TK key) + T* shgetp(T*, char* key) + T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar) + T* hmgetp_null(T*, TK key) + T* shgetp_null(T*, char *key) + Returns a pointer to the structure corresponding to 'key' in + the hashmap. Functions ending in "_null" return NULL if the key + is not present in the hashmap; the others return a pointer to a + structure holding the default value (but not the searched-for key). + + hmdefault + shdefault + TV hmdefault(T*, TV value) + TV shdefault(T*, TV value) + Sets the default value for the hashmap, the value which will be + returned by hmget/shget if the key is not present. + + hmdefaults + shdefaults + TV hmdefaults(T*, T item) + TV shdefaults(T*, T item) + Sets the default struct for the hashmap, the contents which will be + returned by hmgets/shgets if the key is not present. + + hmput + shput + TV hmput(T*, TK key, TV value) + TV shput(T*, char* key, TV value) + Inserts a pair into the hashmap. If the key is already + present in the hashmap, updates its value. + + hmputs + shputs + T hmputs(T*, T item) + T shputs(T*, T item) + Inserts a struct with T.key into the hashmap. If the struct is already + present in the hashmap, updates it. + + hmdel + shdel + int hmdel(T*, TK key) + int shdel(T*, char* key) + If 'key' is in the hashmap, deletes its entry and returns 1. + Otherwise returns 0. + + Function interface (actually macros) for strings only: + + sh_new_strdup + void sh_new_strdup(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate and free + each string key using realloc/free + + sh_new_arena + void sh_new_arena(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate each string + key to a string arena. Every string key ever used by this + hash table remains in the arena until the arena is freed. + Additionally, any key which is deleted and reinserted will + be allocated multiple times in the string arena. + +NOTES + + * These data structures are realloc'd when they grow, and the macro + "functions" write to the provided pointer. This means: (a) the pointer + must be an lvalue, and (b) the pointer to the data structure is not + stable, and you must maintain it the same as you would a realloc'd + pointer. For example, if you pass a pointer to a dynamic array to a + function which updates it, the function must return back the new + pointer to the caller. This is the price of trying to do this in C. + + * The following are the only functions that are thread-safe on a single data + structure, i.e. can be run in multiple threads simultaneously on the same + data structure + hmlen shlen + hmlenu shlenu + hmget_ts shget_ts + hmgeti_ts shgeti_ts + hmgets_ts shgets_ts + + * You iterate over the contents of a dynamic array and a hashmap in exactly + the same way, using arrlen/hmlen/shlen: + + for (i=0; i < arrlen(foo); ++i) + ... foo[i] ... + + * All operations except arrins/arrdel are O(1) amortized, but individual + operations can be slow, so these data structures may not be suitable + for real time use. Dynamic arrays double in capacity as needed, so + elements are copied an average of once. Hash tables double/halve + their size as needed, with appropriate hysteresis to maintain O(1) + performance. + +NOTES - DYNAMIC ARRAY + + * If you know how long a dynamic array is going to be in advance, you can avoid + extra memory allocations by using arrsetlen to allocate it to that length in + advance and use foo[n] while filling it out, or arrsetcap to allocate the memory + for that length and use arrput/arrpush as normal. + + * Unlike some other versions of the dynamic array, this version should + be safe to use with strict-aliasing optimizations. + +NOTES - HASH MAP + + * For compilers other than GCC and clang (e.g. Visual Studio), for hmput/hmget/hmdel + and variants, the key must be an lvalue (so the macro can take the address of it). + Extensions are used that eliminate this requirement if you're using C99 and later + in GCC or clang, or if you're using C++ in GCC. But note that this can make your + code less portable. + + * To test for presence of a key in a hashmap, just do 'hmgeti(foo,key) >= 0'. + + * The iteration order of your data in the hashmap is determined solely by the + order of insertions and deletions. In particular, if you never delete, new + keys are always added at the end of the array. This will be consistent + across all platforms and versions of the library. However, you should not + attempt to serialize the internal hash table, as the hash is not consistent + between different platforms, and may change with future versions of the library. + + * Use sh_new_arena() for string hashmaps that you never delete from. Initialize + with NULL if you're managing the memory for your strings, or your strings are + never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup(). + @TODO: make an arena variant that garbage collects the strings with a trivial + copy collector into a new arena whenever the table shrinks / rebuilds. Since + current arena recommendation is to only use arena if it never deletes, then + this can just replace current arena implementation. + + * If adversarial input is a serious concern and you're on a 64-bit platform, + enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass + a strong random number to stbds_rand_seed. + + * The default value for the hash table is stored in foo[-1], so if you + use code like 'hmget(T,k)->value = 5' you can accidentally overwrite + the value stored by hmdefault if 'k' is not present. + +CREDITS + + Sean Barrett -- library, idea for dynamic array API/implementation + Per Vognsen -- idea for hash table API/implementation + Rafael Sachetto -- arrpop() + github:HeroicKatora -- arraddn() reworking + + Bugfixes: + Andy Durdin + Shane Liesegang + Vinh Truong + Andreas Molzer + github:hashitaku + github:srdjanstipic + Macoy Madson + Andreas Vennstrom + Tobias Mansfield-Williams +*/ + +#ifdef STBDS_UNIT_TESTS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifndef INCLUDE_STB_DS_H +#define INCLUDE_STB_DS_H + +#include +#include + +#ifndef STBDS_NO_SHORT_NAMES +#define arrlen stbds_arrlen +#define arrlenu stbds_arrlenu +#define arrput stbds_arrput +#define arrpush stbds_arrput +#define arrpop stbds_arrpop +#define arrfree stbds_arrfree +#define arraddn stbds_arraddn // deprecated, use one of the following instead: +#define arraddnptr stbds_arraddnptr +#define arraddnindex stbds_arraddnindex +#define arrsetlen stbds_arrsetlen +#define arrlast stbds_arrlast +#define arrins stbds_arrins +#define arrinsn stbds_arrinsn +#define arrdel stbds_arrdel +#define arrdeln stbds_arrdeln +#define arrdelswap stbds_arrdelswap +#define arrcap stbds_arrcap +#define arrsetcap stbds_arrsetcap + +#define hmput stbds_hmput +#define hmputs stbds_hmputs +#define hmget stbds_hmget +#define hmget_ts stbds_hmget_ts +#define hmgets stbds_hmgets +#define hmgetp stbds_hmgetp +#define hmgetp_ts stbds_hmgetp_ts +#define hmgetp_null stbds_hmgetp_null +#define hmgeti stbds_hmgeti +#define hmgeti_ts stbds_hmgeti_ts +#define hmdel stbds_hmdel +#define hmlen stbds_hmlen +#define hmlenu stbds_hmlenu +#define hmfree stbds_hmfree +#define hmdefault stbds_hmdefault +#define hmdefaults stbds_hmdefaults + +#define shput stbds_shput +#define shputi stbds_shputi +#define shputs stbds_shputs +#define shget stbds_shget +#define shgeti stbds_shgeti +#define shgets stbds_shgets +#define shgetp stbds_shgetp +#define shgetp_null stbds_shgetp_null +#define shdel stbds_shdel +#define shlen stbds_shlen +#define shlenu stbds_shlenu +#define shfree stbds_shfree +#define shdefault stbds_shdefault +#define shdefaults stbds_shdefaults +#define sh_new_arena stbds_sh_new_arena +#define sh_new_strdup stbds_sh_new_strdup + +#define stralloc stbds_stralloc +#define strreset stbds_strreset +#endif + +#if defined(STBDS_REALLOC) && !defined(STBDS_FREE) || !defined(STBDS_REALLOC) && defined(STBDS_FREE) +#error "You must define both STBDS_REALLOC and STBDS_FREE, or neither." +#endif +#if !defined(STBDS_REALLOC) && !defined(STBDS_FREE) +#include +#define STBDS_REALLOC(c,p,s) realloc(p,s) +#define STBDS_FREE(c,p) free(p) +#endif + +#ifdef _MSC_VER +#define STBDS_NOTUSED(v) (void)(v) +#else +#define STBDS_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// for security against attackers, seed the library with a random number, at least time() but stronger is better +extern void stbds_rand_seed(size_t seed); + +// these are the hash functions used internally if you want to test them or use them for other purposes +extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed); +extern size_t stbds_hash_string(char *str, size_t seed); + +// this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'. +typedef struct stbds_string_arena stbds_string_arena; +extern char * stbds_stralloc(stbds_string_arena *a, char *str); +extern void stbds_strreset(stbds_string_arena *a); + +// have to #define STBDS_UNIT_TESTS to call this +extern void stbds_unit_tests(void); + +/////////////// +// +// Everything below here is implementation details +// + +extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap); +extern void stbds_arrfreef(void *a); +extern void stbds_hmfree_func(void *p, size_t elemsize); +extern void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode); +extern void * stbds_hmput_default(void *a, size_t elemsize); +extern void * stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode); +extern void * stbds_shmode_func(size_t elemsize, int mode); + +#ifdef __cplusplus +} +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define STBDS_HAS_TYPEOF +#ifdef __cplusplus +//#define STBDS_HAS_LITERAL_ARRAY // this is currently broken for clang +#endif +#endif + +#if !defined(__cplusplus) +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define STBDS_HAS_LITERAL_ARRAY +#endif +#endif + +// this macro takes the address of the argument, but on gcc/clang can accept rvalues +#if defined(STBDS_HAS_LITERAL_ARRAY) && defined(STBDS_HAS_TYPEOF) + #if __clang__ + #define STBDS_ADDRESSOF(typevar, value) ((__typeof__(typevar)[1]){value}) // literal array decays to pointer to value + #else + #define STBDS_ADDRESSOF(typevar, value) ((typeof(typevar)[1]){value}) // literal array decays to pointer to value + #endif +#else +#define STBDS_ADDRESSOF(typevar, value) &(value) +#endif + +#define STBDS_OFFSETOF(var,field) ((char *) &(var)->field - (char *) (var)) + +#define stbds_header(t) ((stbds_array_header *) (t) - 1) +#define stbds_temp(t) stbds_header(t)->temp +#define stbds_temp_key(t) (*(char **) stbds_header(t)->hash_table) + +#define stbds_arrsetcap(a,n) (stbds_arrgrow(a,0,n)) +#define stbds_arrsetlen(a,n) ((stbds_arrcap(a) < (size_t) (n) ? stbds_arrsetcap((a),(size_t)(n)),0 : 0), (a) ? stbds_header(a)->length = (size_t) (n) : 0) +#define stbds_arrcap(a) ((a) ? stbds_header(a)->capacity : 0) +#define stbds_arrlen(a) ((a) ? (ptrdiff_t) stbds_header(a)->length : 0) +#define stbds_arrlenu(a) ((a) ? stbds_header(a)->length : 0) +#define stbds_arrput(a,v) (stbds_arrmaybegrow(a,1), (a)[stbds_header(a)->length++] = (v)) +#define stbds_arrpush stbds_arrput // synonym +#define stbds_arrpop(a) (stbds_header(a)->length--, (a)[stbds_header(a)->length]) +#define stbds_arraddn(a,n) ((void)(stbds_arraddnindex(a, n))) // deprecated, use one of the following instead: +#define stbds_arraddnptr(a,n) (stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), &(a)[stbds_header(a)->length-(n)]) : (a)) +#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a)) +#define stbds_arraddnoff stbds_arraddnindex +#define stbds_arrlast(a) ((a)[stbds_header(a)->length-1]) +#define stbds_arrfree(a) ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL) +#define stbds_arrdel(a,i) stbds_arrdeln(a,i,1) +#define stbds_arrdeln(a,i,n) (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n)) +#define stbds_arrdelswap(a,i) ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1) +#define stbds_arrinsn(a,i,n) (stbds_arraddn((a),(n)), memmove(&(a)[(i)+(n)], &(a)[i], sizeof *(a) * (stbds_header(a)->length-(n)-(i)))) +#define stbds_arrins(a,i,v) (stbds_arrinsn((a),(i),1), (a)[i]=(v)) + +#define stbds_arrmaybegrow(a,n) ((!(a) || stbds_header(a)->length + (n) > stbds_header(a)->capacity) \ + ? (stbds_arrgrow(a,n,0),0) : 0) + +#define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c))) + +#define stbds_hmput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \ + (t)[stbds_temp((t)-1)].key = (k), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_hmputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \ + (t)[stbds_temp((t)-1)] = (s)) + +#define stbds_hmgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \ + stbds_temp((t)-1)) + +#define stbds_hmgeti_ts(t,k,temp) \ + ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \ + (temp)) + +#define stbds_hmgetp(t, k) \ + ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_hmgetp_ts(t, k, temp) \ + ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp]) + +#define stbds_hmdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0) + +#define stbds_hmdefault(t, v) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v)) + +#define stbds_hmdefaults(t, s) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s)) + +#define stbds_hmfree(p) \ + ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL) + +#define stbds_hmgets(t, k) (*stbds_hmgetp(t,k)) +#define stbds_hmget(t, k) (stbds_hmgetp(t,k)->value) +#define stbds_hmget_ts(t, k, temp) (stbds_hmgetp_ts(t,k,temp)->value) +#define stbds_hmlen(t) ((t) ? (ptrdiff_t) stbds_header((t)-1)->length-1 : 0) +#define stbds_hmlenu(t) ((t) ? stbds_header((t)-1)->length-1 : 0) +#define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) + +#define stbds_shput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_shputi(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1)) + +#define stbds_shputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)] = (s), \ + (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally + +#define stbds_pshput(t, p) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \ + (t)[stbds_temp((t)-1)] = (p)) + +#define stbds_shgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + stbds_temp((t)-1)) + +#define stbds_pshgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \ + stbds_temp((t)-1)) + +#define stbds_shgetp(t, k) \ + ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_pshget(t, k) \ + ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)]) + +#define stbds_shdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0) +#define stbds_pshdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0) + +#define stbds_sh_new_arena(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA)) +#define stbds_sh_new_strdup(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP)) + +#define stbds_shdefault(t, v) stbds_hmdefault(t,v) +#define stbds_shdefaults(t, s) stbds_hmdefaults(t,s) + +#define stbds_shfree stbds_hmfree +#define stbds_shlenu stbds_hmlenu + +#define stbds_shgets(t, k) (*stbds_shgetp(t,k)) +#define stbds_shget(t, k) (stbds_shgetp(t,k)->value) +#define stbds_shgetp_null(t,k) (stbds_shgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) +#define stbds_shlen stbds_hmlen + +typedef struct +{ + size_t length; + size_t capacity; + void * hash_table; + ptrdiff_t temp; +} stbds_array_header; + +typedef struct stbds_string_block +{ + struct stbds_string_block *next; + char storage[8]; +} stbds_string_block; + +struct stbds_string_arena +{ + stbds_string_block *storage; + size_t remaining; + unsigned char block; + unsigned char mode; // this isn't used by the string arena itself +}; + +#define STBDS_HM_BINARY 0 +#define STBDS_HM_STRING 1 + +enum +{ + STBDS_SH_NONE, + STBDS_SH_DEFAULT, + STBDS_SH_STRDUP, + STBDS_SH_ARENA +}; + +#ifdef __cplusplus +// in C we use implicit assignment from these void*-returning functions to T*. +// in C++ these templates make the same code work +template static T * stbds_arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) { + return (T*)stbds_arrgrowf((void *)a, elemsize, addlen, min_cap); +} +template static T * stbds_hmget_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmget_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmget_key_ts_wrapper(T *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) { + return (T*)stbds_hmget_key_ts((void*)a, elemsize, key, keysize, temp, mode); +} +template static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) { + return (T*)stbds_hmput_default((void *)a, elemsize); +} +template static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmdel_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode){ + return (T*)stbds_hmdel_key((void*)a, elemsize, key, keysize, keyoffset, mode); +} +template static T * stbds_shmode_func_wrapper(T *, size_t elemsize, int mode) { + return (T*)stbds_shmode_func(elemsize, mode); +} +#else +#define stbds_arrgrowf_wrapper stbds_arrgrowf +#define stbds_hmget_key_wrapper stbds_hmget_key +#define stbds_hmget_key_ts_wrapper stbds_hmget_key_ts +#define stbds_hmput_default_wrapper stbds_hmput_default +#define stbds_hmput_key_wrapper stbds_hmput_key +#define stbds_hmdel_key_wrapper stbds_hmdel_key +#define stbds_shmode_func_wrapper(t,e,m) stbds_shmode_func(e,m) +#endif + +#endif // INCLUDE_STB_DS_H + + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// + +#ifdef STB_DS_IMPLEMENTATION +#include +#include + +#ifndef STBDS_ASSERT +#define STBDS_ASSERT_WAS_UNDEFINED +#define STBDS_ASSERT(x) ((void) 0) +#endif + +#ifdef STBDS_STATISTICS +#define STBDS_STATS(x) x +size_t stbds_array_grow; +size_t stbds_hash_grow; +size_t stbds_hash_shrink; +size_t stbds_hash_rebuild; +size_t stbds_hash_probes; +size_t stbds_hash_alloc; +size_t stbds_rehash_probes; +size_t stbds_rehash_items; +#else +#define STBDS_STATS(x) +#endif + +// +// stbds_arr implementation +// + +//int *prev_allocs[65536]; +//int num_prev; + +void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap) +{ + stbds_array_header temp={0}; // force debugging + void *b; + size_t min_len = stbds_arrlen(a) + addlen; + (void) sizeof(temp); + + // compute the minimum capacity needed + if (min_len > min_cap) + min_cap = min_len; + + if (min_cap <= stbds_arrcap(a)) + return a; + + // increase needed capacity to guarantee O(1) amortized + if (min_cap < 2 * stbds_arrcap(a)) + min_cap = 2 * stbds_arrcap(a); + else if (min_cap < 4) + min_cap = 4; + + //if (num_prev < 65536) if (a) prev_allocs[num_prev++] = (int *) ((char *) a+1); + //if (num_prev == 2201) + // num_prev = num_prev; + b = STBDS_REALLOC(NULL, (a) ? stbds_header(a) : 0, elemsize * min_cap + sizeof(stbds_array_header)); + //if (num_prev < 65536) prev_allocs[num_prev++] = (int *) (char *) b; + b = (char *) b + sizeof(stbds_array_header); + if (a == NULL) { + stbds_header(b)->length = 0; + stbds_header(b)->hash_table = 0; + stbds_header(b)->temp = 0; + } else { + STBDS_STATS(++stbds_array_grow); + } + stbds_header(b)->capacity = min_cap; + + return b; +} + +void stbds_arrfreef(void *a) +{ + STBDS_FREE(NULL, stbds_header(a)); +} + +// +// stbds_hm hash table implementation +// + +#ifdef STBDS_INTERNAL_SMALL_BUCKET +#define STBDS_BUCKET_LENGTH 4 +#else +#define STBDS_BUCKET_LENGTH 8 +#endif + +#define STBDS_BUCKET_SHIFT (STBDS_BUCKET_LENGTH == 8 ? 3 : 2) +#define STBDS_BUCKET_MASK (STBDS_BUCKET_LENGTH-1) +#define STBDS_CACHE_LINE_SIZE 64 + +#define STBDS_ALIGN_FWD(n,a) (((n) + (a) - 1) & ~((a)-1)) + +typedef struct +{ + size_t hash [STBDS_BUCKET_LENGTH]; + ptrdiff_t index[STBDS_BUCKET_LENGTH]; +} stbds_hash_bucket; // in 32-bit, this is one 64-byte cache line; in 64-bit, each array is one 64-byte cache line + +typedef struct +{ + char * temp_key; // this MUST be the first field of the hash table + size_t slot_count; + size_t used_count; + size_t used_count_threshold; + size_t used_count_shrink_threshold; + size_t tombstone_count; + size_t tombstone_count_threshold; + size_t seed; + size_t slot_count_log2; + stbds_string_arena string; + stbds_hash_bucket *storage; // not a separate allocation, just 64-byte aligned storage after this struct +} stbds_hash_index; + +#define STBDS_INDEX_EMPTY -1 +#define STBDS_INDEX_DELETED -2 +#define STBDS_INDEX_IN_USE(x) ((x) >= 0) + +#define STBDS_HASH_EMPTY 0 +#define STBDS_HASH_DELETED 1 + +static size_t stbds_hash_seed=0x31415926; + +void stbds_rand_seed(size_t seed) +{ + stbds_hash_seed = seed; +} + +#define stbds_load_32_or_64(var, temp, v32, v64_hi, v64_lo) \ + temp = v64_lo ^ v32, temp <<= 16, temp <<= 16, temp >>= 16, temp >>= 16, /* discard if 32-bit */ \ + var = v64_hi, var <<= 16, var <<= 16, /* discard if 32-bit */ \ + var ^= temp ^ v32 + +#define STBDS_SIZE_T_BITS ((sizeof (size_t)) * 8) + +static size_t stbds_probe_position(size_t hash, size_t slot_count, size_t slot_log2) +{ + size_t pos; + STBDS_NOTUSED(slot_log2); + pos = hash & (slot_count-1); + #ifdef STBDS_INTERNAL_BUCKET_START + pos &= ~STBDS_BUCKET_MASK; + #endif + return pos; +} + +static size_t stbds_log2(size_t slot_count) +{ + size_t n=0; + while (slot_count > 1) { + slot_count >>= 1; + ++n; + } + return n; +} + +static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_index *ot) +{ + stbds_hash_index *t; + t = (stbds_hash_index *) STBDS_REALLOC(NULL,0,(slot_count >> STBDS_BUCKET_SHIFT) * sizeof(stbds_hash_bucket) + sizeof(stbds_hash_index) + STBDS_CACHE_LINE_SIZE-1); + t->storage = (stbds_hash_bucket *) STBDS_ALIGN_FWD((size_t) (t+1), STBDS_CACHE_LINE_SIZE); + t->slot_count = slot_count; + t->slot_count_log2 = stbds_log2(slot_count); + t->tombstone_count = 0; + t->used_count = 0; + + #if 0 // A1 + t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + #elif 1 // A2 + //t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + //t->tombstone_count_threshold = slot_count* 3/16; // if tombstones are 3/16th of table, rebuild + //t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + + // compute without overflowing + t->used_count_threshold = slot_count - (slot_count>>2); + t->tombstone_count_threshold = (slot_count>>3) + (slot_count>>4); + t->used_count_shrink_threshold = slot_count >> 2; + + #elif 0 // B1 + t->used_count_threshold = slot_count*13/16; // if 13/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 5/16; // if table is only 5/16th full, shrink + #else // C1 + t->used_count_threshold = slot_count*14/16; // if 14/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 6/16; // if table is only 6/16th full, shrink + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // A1 A2 B1 C1 + // 0.10ms : 0.10ms : 0.10ms : 0.11ms : 2,000 inserts creating 2K table + // 0.96ms : 0.95ms : 0.97ms : 1.04ms : 20,000 inserts creating 20K table + // 14.48ms : 14.46ms : 10.63ms : 11.00ms : 200,000 inserts creating 200K table + // 195.74ms : 196.35ms : 203.69ms : 214.92ms : 2,000,000 inserts creating 2M table + // 2193.88ms : 2209.22ms : 2285.54ms : 2437.17ms : 20,000,000 inserts creating 20M table + // 65.27ms : 53.77ms : 65.33ms : 65.47ms : 500,000 inserts & deletes in 2K table + // 72.78ms : 62.45ms : 71.95ms : 72.85ms : 500,000 inserts & deletes in 20K table + // 89.47ms : 77.72ms : 96.49ms : 96.75ms : 500,000 inserts & deletes in 200K table + // 97.58ms : 98.14ms : 97.18ms : 97.53ms : 500,000 inserts & deletes in 2M table + // 118.61ms : 119.62ms : 120.16ms : 118.86ms : 500,000 inserts & deletes in 20M table + // 192.11ms : 194.39ms : 196.38ms : 195.73ms : 500,000 inserts & deletes in 200M table + + if (slot_count <= STBDS_BUCKET_LENGTH) + t->used_count_shrink_threshold = 0; + // to avoid infinite loop, we need to guarantee that at least one slot is empty and will terminate probes + STBDS_ASSERT(t->used_count_threshold + t->tombstone_count_threshold < t->slot_count); + STBDS_STATS(++stbds_hash_alloc); + if (ot) { + t->string = ot->string; + // reuse old seed so we can reuse old hashes so below "copy out old data" doesn't do any hashing + t->seed = ot->seed; + } else { + size_t a,b,temp; + memset(&t->string, 0, sizeof(t->string)); + t->seed = stbds_hash_seed; + // LCG + // in 32-bit, a = 2147001325 b = 715136305 + // in 64-bit, a = 2862933555777941757 b = 3037000493 + stbds_load_32_or_64(a,temp, 2147001325, 0x27bb2ee6, 0x87b0b0fd); + stbds_load_32_or_64(b,temp, 715136305, 0, 0xb504f32d); + stbds_hash_seed = stbds_hash_seed * a + b; + } + + { + size_t i,j; + for (i=0; i < slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *b = &t->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->hash[j] = STBDS_HASH_EMPTY; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->index[j] = STBDS_INDEX_EMPTY; + } + } + + // copy out the old data, if any + if (ot) { + size_t i,j; + t->used_count = ot->used_count; + for (i=0; i < ot->slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *ob = &ot->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) { + if (STBDS_INDEX_IN_USE(ob->index[j])) { + size_t hash = ob->hash[j]; + size_t pos = stbds_probe_position(hash, t->slot_count, t->slot_count_log2); + size_t step = STBDS_BUCKET_LENGTH; + STBDS_STATS(++stbds_rehash_items); + for (;;) { + size_t limit,z; + stbds_hash_bucket *bucket; + bucket = &t->storage[pos >> STBDS_BUCKET_SHIFT]; + STBDS_STATS(++stbds_rehash_probes); + + for (z=pos & STBDS_BUCKET_MASK; z < STBDS_BUCKET_LENGTH; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + limit = pos & STBDS_BUCKET_MASK; + for (z = 0; z < limit; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + pos += step; // quadratic probing + step += STBDS_BUCKET_LENGTH; + pos &= (t->slot_count-1); + } + } + done: + ; + } + } + } + + return t; +} + +#define STBDS_ROTATE_LEFT(val, n) (((val) << (n)) | ((val) >> (STBDS_SIZE_T_BITS - (n)))) +#define STBDS_ROTATE_RIGHT(val, n) (((val) >> (n)) | ((val) << (STBDS_SIZE_T_BITS - (n)))) + +size_t stbds_hash_string(char *str, size_t seed) +{ + size_t hash = seed; + while (*str) + hash = STBDS_ROTATE_LEFT(hash, 9) + (unsigned char) *str++; + + // Thomas Wang 64-to-32 bit mix function, hopefully also works in 32 bits + hash ^= seed; + hash = (~hash) + (hash << 18); + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,31); + hash = hash * 21; + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,11); + hash += (hash << 6); + hash ^= STBDS_ROTATE_RIGHT(hash,22); + return hash+seed; +} + +#ifdef STBDS_SIPHASH_2_4 +#define STBDS_SIPHASH_C_ROUNDS 2 +#define STBDS_SIPHASH_D_ROUNDS 4 +typedef int STBDS_SIPHASH_2_4_can_only_be_used_in_64_bit_builds[sizeof(size_t) == 8 ? 1 : -1]; +#endif + +#ifndef STBDS_SIPHASH_C_ROUNDS +#define STBDS_SIPHASH_C_ROUNDS 1 +#endif +#ifndef STBDS_SIPHASH_D_ROUNDS +#define STBDS_SIPHASH_D_ROUNDS 1 +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4127) // conditional expression is constant, for do..while(0) and sizeof()== +#endif + +static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed) +{ + unsigned char *d = (unsigned char *) p; + size_t i,j; + size_t v0,v1,v2,v3, data; + + // hash that works on 32- or 64-bit registers without knowing which we have + // (computes different results on 32-bit and 64-bit platform) + // derived from siphash, but on 32-bit platforms very different as it uses 4 32-bit state not 4 64-bit + v0 = ((((size_t) 0x736f6d65 << 16) << 16) + 0x70736575) ^ seed; + v1 = ((((size_t) 0x646f7261 << 16) << 16) + 0x6e646f6d) ^ ~seed; + v2 = ((((size_t) 0x6c796765 << 16) << 16) + 0x6e657261) ^ seed; + v3 = ((((size_t) 0x74656462 << 16) << 16) + 0x79746573) ^ ~seed; + + #ifdef STBDS_TEST_SIPHASH_2_4 + // hardcoded with key material in the siphash test vectors + v0 ^= 0x0706050403020100ull ^ seed; + v1 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + v2 ^= 0x0706050403020100ull ^ seed; + v3 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + #endif + + #define STBDS_SIPROUND() \ + do { \ + v0 += v1; v1 = STBDS_ROTATE_LEFT(v1, 13); v1 ^= v0; v0 = STBDS_ROTATE_LEFT(v0,STBDS_SIZE_T_BITS/2); \ + v2 += v3; v3 = STBDS_ROTATE_LEFT(v3, 16); v3 ^= v2; \ + v2 += v1; v1 = STBDS_ROTATE_LEFT(v1, 17); v1 ^= v2; v2 = STBDS_ROTATE_LEFT(v2,STBDS_SIZE_T_BITS/2); \ + v0 += v3; v3 = STBDS_ROTATE_LEFT(v3, 21); v3 ^= v0; \ + } while (0) + + for (i=0; i+sizeof(size_t) <= len; i += sizeof(size_t), d += sizeof(size_t)) { + data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4 + + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + } + data = len << (STBDS_SIZE_T_BITS-8); + switch (len - i) { + case 7: data |= ((size_t) d[6] << 24) << 24; // fall through + case 6: data |= ((size_t) d[5] << 20) << 20; // fall through + case 5: data |= ((size_t) d[4] << 16) << 16; // fall through + case 4: data |= (d[3] << 24); // fall through + case 3: data |= (d[2] << 16); // fall through + case 2: data |= (d[1] << 8); // fall through + case 1: data |= d[0]; // fall through + case 0: break; + } + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + v2 ^= 0xff; + for (j=0; j < STBDS_SIPHASH_D_ROUNDS; ++j) + STBDS_SIPROUND(); + +#ifdef STBDS_SIPHASH_2_4 + return v0^v1^v2^v3; +#else + return v1^v2^v3; // slightly stronger since v0^v3 in above cancels out final round operation? I tweeted at the authors of SipHash about this but they didn't reply +#endif +} + +size_t stbds_hash_bytes(void *p, size_t len, size_t seed) +{ +#ifdef STBDS_SIPHASH_2_4 + return stbds_siphash_bytes(p,len,seed); +#else + unsigned char *d = (unsigned char *) p; + + if (len == 4) { + unsigned int hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + #if 0 + // HASH32-A Bob Jenkin's hash function w/o large constants + hash ^= seed; + hash -= (hash<<6); + hash ^= (hash>>17); + hash -= (hash<<9); + hash ^= seed; + hash ^= (hash<<4); + hash -= (hash<<3); + hash ^= (hash<<10); + hash ^= (hash>>15); + #elif 1 + // HASH32-BB Bob Jenkin's presumably-accidental version of Thomas Wang hash with rotates turned into shifts. + // Note that converting these back to rotates makes it run a lot slower, presumably due to collisions, so I'm + // not really sure what's going on. + hash ^= seed; + hash = (hash ^ 61) ^ (hash >> 16); + hash = hash + (hash << 3); + hash = hash ^ (hash >> 4); + hash = hash * 0x27d4eb2d; + hash ^= seed; + hash = hash ^ (hash >> 15); + #else // HASH32-C - Murmur3 + hash ^= seed; + hash *= 0xcc9e2d51; + hash = (hash << 17) | (hash >> 15); + hash *= 0x1b873593; + hash ^= seed; + hash = (hash << 19) | (hash >> 13); + hash = hash*5 + 0xe6546b64; + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= seed; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // HASH32-A // HASH32-BB // HASH32-C + // 0.10ms // 0.10ms // 0.10ms : 2,000 inserts creating 2K table + // 0.96ms // 0.95ms // 0.99ms : 20,000 inserts creating 20K table + // 14.69ms // 14.43ms // 14.97ms : 200,000 inserts creating 200K table + // 199.99ms // 195.36ms // 202.05ms : 2,000,000 inserts creating 2M table + // 2234.84ms // 2187.74ms // 2240.38ms : 20,000,000 inserts creating 20M table + // 55.68ms // 53.72ms // 57.31ms : 500,000 inserts & deletes in 2K table + // 63.43ms // 61.99ms // 65.73ms : 500,000 inserts & deletes in 20K table + // 80.04ms // 77.96ms // 81.83ms : 500,000 inserts & deletes in 200K table + // 100.42ms // 97.40ms // 102.39ms : 500,000 inserts & deletes in 2M table + // 119.71ms // 120.59ms // 121.63ms : 500,000 inserts & deletes in 20M table + // 185.28ms // 195.15ms // 187.74ms : 500,000 inserts & deletes in 200M table + // 15.58ms // 14.79ms // 15.52ms : 200,000 inserts creating 200K table with varying key spacing + + return (((size_t) hash << 16 << 16) | hash) ^ seed; + } else if (len == 8 && sizeof(size_t) == 8) { + size_t hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + hash |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // avoid warning if size_t == 4 + hash ^= seed; + hash = (~hash) + (hash << 21); + hash ^= STBDS_ROTATE_RIGHT(hash,24); + hash *= 265; + hash ^= STBDS_ROTATE_RIGHT(hash,14); + hash ^= seed; + hash *= 21; + hash ^= STBDS_ROTATE_RIGHT(hash,28); + hash += (hash << 31); + hash = (~hash) + (hash << 18); + return hash; + } else { + return stbds_siphash_bytes(p,len,seed); + } +#endif +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +static int stbds_is_key_equal(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode, size_t i) +{ + if (mode >= STBDS_HM_STRING) + return 0==strcmp((char *) key, * (char **) ((char *) a + elemsize*i + keyoffset)); + else + return 0==memcmp(key, (char *) a + elemsize*i + keyoffset, keysize); +} + +#define STBDS_HASH_TO_ARR(x,elemsize) ((char*) (x) - (elemsize)) +#define STBDS_ARR_TO_HASH(x,elemsize) ((char*) (x) + (elemsize)) + +#define stbds_hash_table(a) ((stbds_hash_index *) stbds_header(a)->hash_table) + +void stbds_hmfree_func(void *a, size_t elemsize) +{ + if (a == NULL) return; + if (stbds_hash_table(a) != NULL) { + if (stbds_hash_table(a)->string.mode == STBDS_SH_STRDUP) { + size_t i; + // skip 0th element, which is default + for (i=1; i < stbds_header(a)->length; ++i) + STBDS_FREE(NULL, *(char**) ((char *) a + elemsize*i)); + } + stbds_strreset(&stbds_hash_table(a)->string); + } + STBDS_FREE(NULL, stbds_header(a)->hash_table); + STBDS_FREE(NULL, stbds_header(a)); +} + +static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + stbds_hash_index *table = stbds_hash_table(raw_a); + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t limit,i; + size_t pos; + stbds_hash_bucket *bucket; + + if (hash < 2) hash += 2; // stored hash values are forbidden from being 0, so we can detect empty slots + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket, this should help performance on small hash tables that fit in cache + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + /* NOTREACHED */ +} + +void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) +{ + size_t keyoffset = 0; + if (a == NULL) { + // make it non-empty so we can return a temp + a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + *temp = STBDS_INDEX_EMPTY; + // adjust a to point after the default element + return STBDS_ARR_TO_HASH(a,elemsize); + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + // adjust a to point to the default element + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + if (table == 0) { + *temp = -1; + } else { + ptrdiff_t slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) { + *temp = STBDS_INDEX_EMPTY; + } else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + *temp = b->index[slot & STBDS_BUCKET_MASK]; + } + } + return a; + } +} + +void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + ptrdiff_t temp; + void *p = stbds_hmget_key_ts(a, elemsize, key, keysize, &temp, mode); + stbds_temp(STBDS_HASH_TO_ARR(p,elemsize)) = temp; + return p; +} + +void * stbds_hmput_default(void *a, size_t elemsize) +{ + // three cases: + // a is NULL <- allocate + // a has a hash table but no entries, because of shmode <- grow + // a has entries <- do nothing + if (a == NULL || stbds_header(STBDS_HASH_TO_ARR(a,elemsize))->length == 0) { + a = stbds_arrgrowf(a ? STBDS_HASH_TO_ARR(a,elemsize) : NULL, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + a=STBDS_ARR_TO_HASH(a,elemsize); + } + return a; +} + +static char *stbds_strdup(char *str); + +void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + size_t keyoffset=0; + void *raw_a; + stbds_hash_index *table; + + if (a == NULL) { + a = stbds_arrgrowf(0, elemsize, 0, 1); + memset(a, 0, elemsize); + stbds_header(a)->length += 1; + // adjust a to point AFTER the default element + a = STBDS_ARR_TO_HASH(a,elemsize); + } + + // adjust a to point to the default element + raw_a = a; + a = STBDS_HASH_TO_ARR(a,elemsize); + + table = (stbds_hash_index *) stbds_header(a)->hash_table; + + if (table == NULL || table->used_count >= table->used_count_threshold) { + stbds_hash_index *nt; + size_t slot_count; + + slot_count = (table == NULL) ? STBDS_BUCKET_LENGTH : table->slot_count*2; + nt = stbds_make_hash_index(slot_count, table); + if (table) + STBDS_FREE(NULL, table); + else + nt->string.mode = mode >= STBDS_HM_STRING ? STBDS_SH_DEFAULT : 0; + stbds_header(a)->hash_table = table = nt; + STBDS_STATS(++stbds_hash_grow); + } + + // we iterate hash table explicitly because we want to track if we saw a tombstone + { + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t pos; + ptrdiff_t tombstone = -1; + stbds_hash_bucket *bucket; + + // stored hash values are forbidden from being 0, so we can detect empty slots to early out quickly + if (hash < 2) hash += 2; + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + size_t limit, i; + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + if (mode >= STBDS_HM_STRING) + stbds_temp_key(a) = * (char **) ((char *) raw_a + elemsize*bucket->index[i] + keyoffset); + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + found_empty_slot: + if (tombstone >= 0) { + pos = tombstone; + --table->tombstone_count; + } + ++table->used_count; + + { + ptrdiff_t i = (ptrdiff_t) stbds_arrlen(a); + // we want to do stbds_arraddn(1), but we can't use the macros since we don't have something of the right type + if ((size_t) i+1 > stbds_arrcap(a)) + *(void **) &a = stbds_arrgrowf(a, elemsize, 1, 0); + raw_a = STBDS_ARR_TO_HASH(a,elemsize); + + STBDS_ASSERT((size_t) i+1 <= stbds_arrcap(a)); + stbds_header(a)->length = i+1; + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + bucket->hash[pos & STBDS_BUCKET_MASK] = hash; + bucket->index[pos & STBDS_BUCKET_MASK] = i-1; + stbds_temp(a) = i-1; + + switch (table->string.mode) { + case STBDS_SH_STRDUP: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_strdup((char*) key); break; + case STBDS_SH_ARENA: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_stralloc(&table->string, (char*)key); break; + case STBDS_SH_DEFAULT: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = (char *) key; break; + default: memcpy((char *) a + elemsize*i, key, keysize); break; + } + } + return STBDS_ARR_TO_HASH(a,elemsize); + } +} + +void * stbds_shmode_func(size_t elemsize, int mode) +{ + void *a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_hash_index *h; + memset(a, 0, elemsize); + stbds_header(a)->length = 1; + stbds_header(a)->hash_table = h = (stbds_hash_index *) stbds_make_hash_index(STBDS_BUCKET_LENGTH, NULL); + h->string.mode = (unsigned char) mode; + return STBDS_ARR_TO_HASH(a,elemsize); +} + +void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + if (a == NULL) { + return 0; + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + stbds_temp(raw_a) = 0; + if (table == 0) { + return a; + } else { + ptrdiff_t slot; + slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) + return a; + else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + int i = slot & STBDS_BUCKET_MASK; + ptrdiff_t old_index = b->index[i]; + ptrdiff_t final_index = (ptrdiff_t) stbds_arrlen(raw_a)-1-1; // minus one for the raw_a vs a, and minus one for 'last' + STBDS_ASSERT(slot < (ptrdiff_t) table->slot_count); + --table->used_count; + ++table->tombstone_count; + stbds_temp(raw_a) = 1; + STBDS_ASSERT(table->used_count >= 0); + //STBDS_ASSERT(table->tombstone_count < table->slot_count/4); + b->hash[i] = STBDS_HASH_DELETED; + b->index[i] = STBDS_INDEX_DELETED; + + if (mode == STBDS_HM_STRING && table->string.mode == STBDS_SH_STRDUP) + STBDS_FREE(NULL, *(char**) ((char *) a+elemsize*old_index)); + + // if indices are the same, memcpy is a no-op, but back-pointer-fixup will fail, so skip + if (old_index != final_index) { + // swap delete + memmove((char*) a + elemsize*old_index, (char*) a + elemsize*final_index, elemsize); + + // now find the slot for the last element + if (mode == STBDS_HM_STRING) + slot = stbds_hm_find_slot(a, elemsize, *(char**) ((char *) a+elemsize*old_index + keyoffset), keysize, keyoffset, mode); + else + slot = stbds_hm_find_slot(a, elemsize, (char* ) a+elemsize*old_index + keyoffset, keysize, keyoffset, mode); + STBDS_ASSERT(slot >= 0); + b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + i = slot & STBDS_BUCKET_MASK; + STBDS_ASSERT(b->index[i] == final_index); + b->index[i] = old_index; + } + stbds_header(raw_a)->length -= 1; + + if (table->used_count < table->used_count_shrink_threshold && table->slot_count > STBDS_BUCKET_LENGTH) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count>>1, table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_shrink); + } else if (table->tombstone_count > table->tombstone_count_threshold) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count , table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_rebuild); + } + + return a; + } + } + } + /* NOTREACHED */ +} + +static char *stbds_strdup(char *str) +{ + // to keep replaceable allocator simple, we don't want to use strdup. + // rolling our own also avoids problem of strdup vs _strdup + size_t len = strlen(str)+1; + char *p = (char*) STBDS_REALLOC(NULL, 0, len); + memmove(p, str, len); + return p; +} + +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MIN +#define STBDS_STRING_ARENA_BLOCKSIZE_MIN 512u +#endif +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MAX +#define STBDS_STRING_ARENA_BLOCKSIZE_MAX (1u<<20) +#endif + +char *stbds_stralloc(stbds_string_arena *a, char *str) +{ + char *p; + size_t len = strlen(str)+1; + if (len > a->remaining) { + // compute the next blocksize + size_t blocksize = a->block; + + // size is 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, etc., so that + // there are log(SIZE) allocations to free when we destroy the table + blocksize = (size_t) (STBDS_STRING_ARENA_BLOCKSIZE_MIN) << (blocksize>>1); + + // if size is under 1M, advance to next blocktype + if (blocksize < (size_t)(STBDS_STRING_ARENA_BLOCKSIZE_MAX)) + ++a->block; + + if (len > blocksize) { + // if string is larger than blocksize, then just allocate the full size. + // note that we still advance string_block so block size will continue + // increasing, so e.g. if somebody only calls this with 1000-long strings, + // eventually the arena will start doubling and handling those as well + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + len); + memmove(sb->storage, str, len); + if (a->storage) { + // insert it after the first element, so that we don't waste the space there + sb->next = a->storage->next; + a->storage->next = sb; + } else { + sb->next = 0; + a->storage = sb; + a->remaining = 0; // this is redundant, but good for clarity + } + return sb->storage; + } else { + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + blocksize); + sb->next = a->storage; + a->storage = sb; + a->remaining = blocksize; + } + } + + STBDS_ASSERT(len <= a->remaining); + p = a->storage->storage + a->remaining - len; + a->remaining -= len; + memmove(p, str, len); + return p; +} + +void stbds_strreset(stbds_string_arena *a) +{ + stbds_string_block *x,*y; + x = a->storage; + while (x) { + y = x->next; + STBDS_FREE(NULL, x); + x = y; + } + memset(a, 0, sizeof(*a)); +} + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// UNIT TESTS +// + +#ifdef STBDS_UNIT_TESTS +#include +#ifdef STBDS_ASSERT_WAS_UNDEFINED +#undef STBDS_ASSERT +#endif +#ifndef STBDS_ASSERT +#define STBDS_ASSERT assert +#include +#endif + +typedef struct { int key,b,c,d; } stbds_struct; +typedef struct { int key[2],b,c,d; } stbds_struct2; + +static char buffer[256]; +char *strkey(int n) +{ +#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) + sprintf_s(buffer, sizeof(buffer), "test_%d", n); +#else + sprintf(buffer, "test_%d", n); +#endif + return buffer; +} + +void stbds_unit_tests(void) +{ +#if defined(_MSC_VER) && _MSC_VER <= 1200 && defined(__cplusplus) + // VC6 C++ doesn't like the template<> trick on unnamed structures, so do nothing! + STBDS_ASSERT(0); +#else + const int testsize = 100000; + const int testsize2 = testsize/20; + int *arr=NULL; + struct { int key; int value; } *intmap = NULL; + struct { char *key; int value; } *strmap = NULL, s; + struct { stbds_struct key; int value; } *map = NULL; + stbds_struct *map2 = NULL; + stbds_struct2 *map3 = NULL; + stbds_string_arena sa = { 0 }; + int key3[2] = { 1,2 }; + ptrdiff_t temp; + + int i,j; + + STBDS_ASSERT(arrlen(arr)==0); + for (i=0; i < 20000; i += 50) { + for (j=0; j < i; ++j) + arrpush(arr,j); + arrfree(arr); + } + + for (i=0; i < 4; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdel(arr,i); + arrfree(arr); + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdelswap(arr,i); + arrfree(arr); + } + + for (i=0; i < 5; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + stbds_arrins(arr,i,5); + STBDS_ASSERT(arr[i] == 5); + if (i < 4) + STBDS_ASSERT(arr[4] == 4); + arrfree(arr); + } + + i = 1; + STBDS_ASSERT(hmgeti(intmap,i) == -1); + hmdefault(intmap, -2); + STBDS_ASSERT(hmgeti(intmap, i) == -1); + STBDS_ASSERT(hmget (intmap, i) == -2); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*5); + for (i=0; i < testsize; i+=1) { + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(intmap, i, temp) == -2 ); + else STBDS_ASSERT(hmget_ts(intmap, i, temp) == i*5); + } + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=2; i < testsize; i+=4) + hmdel(intmap, i); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=0; i < testsize; i+=1) + hmdel(intmap, i); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(hmget(intmap, i) == -2 ); + hmfree(intmap); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + hmfree(intmap); + + #if defined(__clang__) || defined(__GNUC__) + #ifndef __cplusplus + intmap = NULL; + hmput(intmap, 15, 7); + hmput(intmap, 11, 3); + hmput(intmap, 9, 5); + STBDS_ASSERT(hmget(intmap, 9) == 5); + STBDS_ASSERT(hmget(intmap, 11) == 3); + STBDS_ASSERT(hmget(intmap, 15) == 7); + #endif + #endif + + for (i=0; i < testsize; ++i) + stralloc(&sa, strkey(i)); + strreset(&sa); + + { + s.key = "a", s.value = 1; + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key == s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_strdup(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_arena(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + for (j=0; j < 2; ++j) { + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + if (j == 0) + sh_new_strdup(strmap); + else + sh_new_arena(strmap); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + shdefault(strmap, -2); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + for (i=0; i < testsize; i+=2) + shput(strmap, strkey(i), i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=2; i < testsize; i+=4) + shdel(strmap, strkey(i)); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=0; i < testsize; i+=1) + shdel(strmap, strkey(i)); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + shfree(strmap); + } + + { + struct { char *key; char value; } *hash = NULL; + char name[4] = "jen"; + shput(hash, "bob" , 'h'); + shput(hash, "sally" , 'e'); + shput(hash, "fred" , 'l'); + shput(hash, "jen" , 'x'); + shput(hash, "doug" , 'o'); + + shput(hash, name , 'l'); + shfree(hash); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmput(map, s, i*5); + } + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3 ,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmget(map, s) == 0); + else STBDS_ASSERT(hmget(map, s) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(map, s, temp) == 0); + else STBDS_ASSERT(hmget_ts(map, s, temp) == i*5); + //STBDS_ASSERT(hmget(map, t.key) == 0); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmputs(map2, s); + } + hmfree(map); + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmgets(map2, s.key).d == 0); + else STBDS_ASSERT(hmgets(map2, s.key).d == i*4); + //STBDS_ASSERT(hmgetp(map2, t.key) == 0); + } + hmfree(map2); + + for (i=0; i < testsize; i += 2) { + stbds_struct2 s = { { i,i*2 }, i*3,i*4, i*5 }; + hmputs(map3, s); + } + for (i=0; i < testsize; i += 1) { + stbds_struct2 s = { { i,i*2}, i*3, i*4, i*5 }; + stbds_struct2 t = { { i,i*2}, i*3+1, i*4, i*5 }; + if (i & 1) STBDS_ASSERT(hmgets(map3, s.key).d == 0); + else STBDS_ASSERT(hmgets(map3, s.key).d == i*5); + //STBDS_ASSERT(hmgetp(map3, t.key) == 0); + } +#endif +} +#endif + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2019 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/mdm/xserver.c b/mdm/xserver.c index 62621d6..e3e1fe2 100644 --- a/mdm/xserver.c +++ b/mdm/xserver.c @@ -1,12 +1,19 @@ +#define _MILSKO +#define USE_X11 #include "mdm.h" -#include +#include "stb_ds.h" static pid_t x_server; static int got_usr1; -static Display* display; + +Display* xdisplay; +pthread_t xthread; +pthread_mutex_t xmutex; static void catch_usr1(int sig){ + (void)sig; + got_usr1 = 1; } @@ -34,25 +41,95 @@ int launch_x(void){ return 0; } -void init_x(void){ - XColor bg; +static int wm_detected = 0; +static int wm_detect(Display* disp, XErrorEvent* ev){ + (void)disp; + (void)ev; - display = XOpenDisplay(NULL); + wm_detected = 1; +} + +static void* x11_thread_routine(void* arg){ + x_loop(); +} + +int init_x(void){ + XColor bg; + void* old; + + xdisplay = XOpenDisplay(NULL); bg.red = 0x1c * 256; bg.green = 0x45 * 256; bg.blue = 0x73 * 256; - XAllocColor(display, DefaultColormap(display, DefaultScreen(display)), &bg); + XAllocColor(xdisplay, DefaultColormap(xdisplay, DefaultScreen(xdisplay)), &bg); - XSetWindowBackground(display, DefaultRootWindow(display), bg.pixel); - XClearWindow(display, DefaultRootWindow(display)); - XFlush(display); + 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) return 1; + + pthread_mutex_init(&xmutex, NULL); + + pthread_create(&xthread, NULL, x11_thread_routine, NULL); + + return 0; } int x_width(void){ - return DisplayWidth(display, DefaultScreen(display)); + return DisplayWidth(xdisplay, DefaultScreen(xdisplay)); } int x_height(void){ - return DisplayHeight(display, DefaultScreen(display)); + return DisplayHeight(xdisplay, DefaultScreen(xdisplay)); +} + +typedef struct window { + MwWidget frame; + Window client; +} window_t; + +void x_loop(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, ev.xmaprequest.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); + + arrput(frames, w); + pthread_mutex_unlock(&xmutex); + } + } } From b8c61560b92f226dd6e4b925ea2e0d90df439d6b Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 18:40:37 +0900 Subject: [PATCH 15/29] add mpanel --- CMakeLists.txt | 1 + mpanel/CMakeLists.txt | 26 ++++++++++++++++++++++++++ mpanel/main.c | 1 + 3 files changed, 28 insertions(+) create mode 100644 mpanel/CMakeLists.txt create mode 100644 mpanel/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index b35678c..f0d4fd0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,3 +28,4 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_subdirectory(mdm) add_subdirectory(milkwm) +add_subdirectory(mpanel) diff --git a/mpanel/CMakeLists.txt b/mpanel/CMakeLists.txt new file mode 100644 index 0000000..2e19543 --- /dev/null +++ b/mpanel/CMakeLists.txt @@ -0,0 +1,26 @@ +project( + mpanel +) + +file( + GLOB + mpanel_SRC + *.c +) + +add_executable( + mpanel + ${mpanel_SRC} +) + +target_link_libraries( + mdm + PRIVATE + MDELib +) + +include(GNUInstallDirs) +install( + TARGETS mpanel + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/mpanel/main.c b/mpanel/main.c new file mode 100644 index 0000000..979a1cd --- /dev/null +++ b/mpanel/main.c @@ -0,0 +1 @@ +int main(){} From 376190445dd14f3a2f6c07286792ab5c93b8e7f8 Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 18:43:14 +0900 Subject: [PATCH 16/29] fix --- milkwm/CMakeLists.txt | 2 +- mpanel/CMakeLists.txt | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/milkwm/CMakeLists.txt b/milkwm/CMakeLists.txt index f5278b8..9c964ed 100644 --- a/milkwm/CMakeLists.txt +++ b/milkwm/CMakeLists.txt @@ -14,7 +14,7 @@ add_executable( ) target_link_libraries( - mdm + milkwm PRIVATE MDELib ) diff --git a/mpanel/CMakeLists.txt b/mpanel/CMakeLists.txt index 2e19543..6fe1d22 100644 --- a/mpanel/CMakeLists.txt +++ b/mpanel/CMakeLists.txt @@ -14,10 +14,17 @@ add_executable( ) target_link_libraries( - mdm + mpanel PRIVATE MDELib ) +if(${CMAKE_SYSTEM_NAME} STREQUAL Linux) + target_link_libraries( + mpanel + PRIVATE + dl + ) +endif() include(GNUInstallDirs) install( From 68e2f173c830a33a545c5abc1dcd2a9b632afb14 Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 19:37:37 +0900 Subject: [PATCH 17/29] pam works --- mdm/login.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++-- mdm/main.c | 8 ++++-- mdm/mdm.h | 4 +++ mdm/xserver.c | 17 ++++++++++++ 4 files changed, 101 insertions(+), 4 deletions(-) diff --git a/mdm/login.c b/mdm/login.c index 2092213..ae82750 100644 --- a/mdm/login.c +++ b/mdm/login.c @@ -1,14 +1,81 @@ #include "mdm.h" +#include + MwWidget root; +MwWidget window, usercombo, passentry; static void add_user(const char* name, void* user){ MwComboBoxAdd(user, -1, name); } +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 = MwComboBoxGet(usercombo, MwGetInteger(usercombo, MwNvalue)); + struct pam_conv conv = {conversation, NULL}; + pam_handle_t* pam; + int ret; + jmp_buf cleanup; + int err = 1; + + (void)handle; + (void)user; + (void)client; + + if(setjmp(cleanup)){ + pam_end(pam, 0); + + if(err){ + MwSetText(passentry, MwNtext, ""); + }else{ + MwDestroyWidget(root); + } + + return; + } + + ret = pam_start("mdm", username, &conv, &pam); + if(ret != PAM_SUCCESS){ + longjmp(cleanup, 1); + } + + ret = pam_authenticate(pam, 0); + if(ret != PAM_SUCCESS){ + longjmp(cleanup, 1); + } + + ret = pam_acct_mgmt(pam, 0); + if(ret != PAM_SUCCESS){ + longjmp(cleanup, 1); + } + + err = 0; + longjmp(cleanup, 1); +} + void login_window(void){ - MwWidget window, pic, userlabel, usercombo, passlabel, passentry, mainsep, sesslabel, sesscombo, fieldsep, shutdown, reboot, ok; + MwWidget pic, userlabel, passlabel, mainsep, sesslabel, sesscombo, fieldsep, shutdown, reboot, ok; MwLLPixmap p; + void* out; MwLibraryInit(); @@ -38,7 +105,7 @@ void login_window(void){ 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, (366 - 362) / 2, 10+137, 362, 10, + fieldsep = MwVaCreateWidget(MwSeparatorClass, "fieldsep", window, 10, 10+137, 351, 10, MwNorientation, MwHORIZONTAL, NULL); shutdown = MwVaCreateWidget(MwButtonClass, "shutdown", window, 10, 10+137+10, 80, 18, @@ -53,6 +120,9 @@ void login_window(void){ if(p != NULL) MwVaApply(pic, MwNpixmap, p, NULL); + MwAddUserHandler(ok, MwNactivateHandler, try_login, NULL); + MwAddUserHandler(passentry, MwNactivateHandler, try_login, NULL); + MDEListUsers(add_user, usercombo); while(1){ @@ -67,4 +137,6 @@ void login_window(void){ MwTimeSleep(30); } + + pthread_join(xthread, &out); } diff --git a/mdm/main.c b/mdm/main.c index bb61394..7507601 100644 --- a/mdm/main.c +++ b/mdm/main.c @@ -25,7 +25,11 @@ int main(int argc, char** argv){ if((st = launch_x()) != 0) return st; } - if((st = init_x()) != 0) return st; + while(1){ + if((st = init_x()) != 0) return st; - login_window(); + login_window(); + + /* TODO: run session */ + } } diff --git a/mdm/mdm.h b/mdm/mdm.h index fe1d9db..60869bd 100644 --- a/mdm/mdm.h +++ b/mdm/mdm.h @@ -3,11 +3,14 @@ #include "config.h" +/* Milsko */ #include #include +/* External */ #include +/* Standard */ #include #include #include @@ -16,6 +19,7 @@ #include #include #include +#include #include /* xserver.c */ diff --git a/mdm/xserver.c b/mdm/xserver.c index e3e1fe2..21b6eed 100644 --- a/mdm/xserver.c +++ b/mdm/xserver.c @@ -51,6 +51,7 @@ static int wm_detect(Display* disp, XErrorEvent* ev){ static void* x11_thread_routine(void* arg){ x_loop(); + XCloseDisplay(xdisplay); } int init_x(void){ @@ -128,8 +129,24 @@ void x_loop(void){ MwNinverted, 0, NULL); + XSelectInput(xdisplay, w.client, StructureNotifyMask); + arrput(frames, w); pthread_mutex_unlock(&xmutex); + }else if(ev.type == UnmapNotify){ + int i; + + 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); + break; + } + } + pthread_mutex_unlock(&xmutex); + + break; } } } From 70040a1270fc68079e0c5a43bf0e2a2b0be23bd9 Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 19:39:34 +0900 Subject: [PATCH 18/29] fix --- mdm/stb_ds.c | 2 +- mdm/stb_ds.h | 1895 -------------------------------------------------- 2 files changed, 1 insertion(+), 1896 deletions(-) delete mode 100644 mdm/stb_ds.h diff --git a/mdm/stb_ds.c b/mdm/stb_ds.c index 9df32fd..689615c 100644 --- a/mdm/stb_ds.c +++ b/mdm/stb_ds.c @@ -1,2 +1,2 @@ #define STB_DS_IMPLEMENTATION -#include "stb_ds.h" +#include diff --git a/mdm/stb_ds.h b/mdm/stb_ds.h deleted file mode 100644 index e84c82d..0000000 --- a/mdm/stb_ds.h +++ /dev/null @@ -1,1895 +0,0 @@ -/* stb_ds.h - v0.67 - public domain data structures - Sean Barrett 2019 - - This is a single-header-file library that provides easy-to-use - dynamic arrays and hash tables for C (also works in C++). - - For a gentle introduction: - http://nothings.org/stb_ds - - To use this library, do this in *one* C or C++ file: - #define STB_DS_IMPLEMENTATION - #include "stb_ds.h" - -TABLE OF CONTENTS - - Table of Contents - Compile-time options - License - Documentation - Notes - Notes - Dynamic arrays - Notes - Hash maps - Credits - -COMPILE-TIME OPTIONS - - #define STBDS_NO_SHORT_NAMES - - This flag needs to be set globally. - - By default stb_ds exposes shorter function names that are not qualified - with the "stbds_" prefix. If these names conflict with the names in your - code, define this flag. - - #define STBDS_SIPHASH_2_4 - - This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION. - - By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for - 4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force - stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes - hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on - 64-byte keys, and 10% slower on 256-byte keys on my test computer. - - #define STBDS_REALLOC(context,ptr,size) better_realloc - #define STBDS_FREE(context,ptr) better_free - - These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION. - - By default stb_ds uses stdlib realloc() and free() for memory management. You can - substitute your own functions instead by defining these symbols. You must either - define both, or neither. Note that at the moment, 'context' will always be NULL. - @TODO add an array/hash initialization function that takes a memory context pointer. - - #define STBDS_UNIT_TESTS - - Defines a function stbds_unit_tests() that checks the functioning of the data structures. - - Note that on older versions of gcc (e.g. 5.x.x) you may need to build with '-std=c++0x' - (or equivalentally '-std=c++11') when using anonymous structures as seen on the web - page or in STBDS_UNIT_TESTS. - -LICENSE - - Placed in the public domain and also MIT licensed. - See end of file for detailed license information. - -DOCUMENTATION - - Dynamic Arrays - - Non-function interface: - - Declare an empty dynamic array of type T - T* foo = NULL; - - Access the i'th item of a dynamic array 'foo' of type T, T* foo: - foo[i] - - Functions (actually macros) - - arrfree: - void arrfree(T*); - Frees the array. - - arrlen: - ptrdiff_t arrlen(T*); - Returns the number of elements in the array. - - arrlenu: - size_t arrlenu(T*); - Returns the number of elements in the array as an unsigned type. - - arrpop: - T arrpop(T* a) - Removes the final element of the array and returns it. - - arrput: - T arrput(T* a, T b); - Appends the item b to the end of array a. Returns b. - - arrins: - T arrins(T* a, int p, T b); - Inserts the item b into the middle of array a, into a[p], - moving the rest of the array over. Returns b. - - arrinsn: - void arrinsn(T* a, int p, int n); - Inserts n uninitialized items into array a starting at a[p], - moving the rest of the array over. - - arraddnptr: - T* arraddnptr(T* a, int n) - Appends n uninitialized items onto array at the end. - Returns a pointer to the first uninitialized item added. - - arraddnindex: - size_t arraddnindex(T* a, int n) - Appends n uninitialized items onto array at the end. - Returns the index of the first uninitialized item added. - - arrdel: - void arrdel(T* a, int p); - Deletes the element at a[p], moving the rest of the array over. - - arrdeln: - void arrdeln(T* a, int p, int n); - Deletes n elements starting at a[p], moving the rest of the array over. - - arrdelswap: - void arrdelswap(T* a, int p); - Deletes the element at a[p], replacing it with the element from - the end of the array. O(1) performance. - - arrsetlen: - void arrsetlen(T* a, int n); - Changes the length of the array to n. Allocates uninitialized - slots at the end if necessary. - - arrsetcap: - size_t arrsetcap(T* a, int n); - Sets the length of allocated storage to at least n. It will not - change the length of the array. - - arrcap: - size_t arrcap(T* a); - Returns the number of total elements the array can contain without - needing to be reallocated. - - Hash maps & String hash maps - - Given T is a structure type: struct { TK key; TV value; }. Note that some - functions do not require TV value and can have other fields. For string - hash maps, TK must be 'char *'. - - Special interface: - - stbds_rand_seed: - void stbds_rand_seed(size_t seed); - For security against adversarially chosen data, you should seed the - library with a strong random number. Or at least seed it with time(). - - stbds_hash_string: - size_t stbds_hash_string(char *str, size_t seed); - Returns a hash value for a string. - - stbds_hash_bytes: - size_t stbds_hash_bytes(void *p, size_t len, size_t seed); - These functions hash an arbitrary number of bytes. The function - uses a custom hash for 4- and 8-byte data, and a weakened version - of SipHash for everything else. On 64-bit platforms you can get - specification-compliant SipHash-2-4 on all data by defining - STBDS_SIPHASH_2_4, at a significant cost in speed. - - Non-function interface: - - Declare an empty hash map of type T - T* foo = NULL; - - Access the i'th entry in a hash table T* foo: - foo[i] - - Function interface (actually macros): - - hmfree - shfree - void hmfree(T*); - void shfree(T*); - Frees the hashmap and sets the pointer to NULL. - - hmlen - shlen - ptrdiff_t hmlen(T*) - ptrdiff_t shlen(T*) - Returns the number of elements in the hashmap. - - hmlenu - shlenu - size_t hmlenu(T*) - size_t shlenu(T*) - Returns the number of elements in the hashmap. - - hmgeti - shgeti - hmgeti_ts - ptrdiff_t hmgeti(T*, TK key) - ptrdiff_t shgeti(T*, char* key) - ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar) - Returns the index in the hashmap which has the key 'key', or -1 - if the key is not present. - - hmget - hmget_ts - shget - TV hmget(T*, TK key) - TV shget(T*, char* key) - TV hmget_ts(T*, TK key, ptrdiff_t tempvar) - Returns the value corresponding to 'key' in the hashmap. - The structure must have a 'value' field - - hmgets - shgets - T hmgets(T*, TK key) - T shgets(T*, char* key) - Returns the structure corresponding to 'key' in the hashmap. - - hmgetp - shgetp - hmgetp_ts - hmgetp_null - shgetp_null - T* hmgetp(T*, TK key) - T* shgetp(T*, char* key) - T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar) - T* hmgetp_null(T*, TK key) - T* shgetp_null(T*, char *key) - Returns a pointer to the structure corresponding to 'key' in - the hashmap. Functions ending in "_null" return NULL if the key - is not present in the hashmap; the others return a pointer to a - structure holding the default value (but not the searched-for key). - - hmdefault - shdefault - TV hmdefault(T*, TV value) - TV shdefault(T*, TV value) - Sets the default value for the hashmap, the value which will be - returned by hmget/shget if the key is not present. - - hmdefaults - shdefaults - TV hmdefaults(T*, T item) - TV shdefaults(T*, T item) - Sets the default struct for the hashmap, the contents which will be - returned by hmgets/shgets if the key is not present. - - hmput - shput - TV hmput(T*, TK key, TV value) - TV shput(T*, char* key, TV value) - Inserts a pair into the hashmap. If the key is already - present in the hashmap, updates its value. - - hmputs - shputs - T hmputs(T*, T item) - T shputs(T*, T item) - Inserts a struct with T.key into the hashmap. If the struct is already - present in the hashmap, updates it. - - hmdel - shdel - int hmdel(T*, TK key) - int shdel(T*, char* key) - If 'key' is in the hashmap, deletes its entry and returns 1. - Otherwise returns 0. - - Function interface (actually macros) for strings only: - - sh_new_strdup - void sh_new_strdup(T*); - Overwrites the existing pointer with a newly allocated - string hashmap which will automatically allocate and free - each string key using realloc/free - - sh_new_arena - void sh_new_arena(T*); - Overwrites the existing pointer with a newly allocated - string hashmap which will automatically allocate each string - key to a string arena. Every string key ever used by this - hash table remains in the arena until the arena is freed. - Additionally, any key which is deleted and reinserted will - be allocated multiple times in the string arena. - -NOTES - - * These data structures are realloc'd when they grow, and the macro - "functions" write to the provided pointer. This means: (a) the pointer - must be an lvalue, and (b) the pointer to the data structure is not - stable, and you must maintain it the same as you would a realloc'd - pointer. For example, if you pass a pointer to a dynamic array to a - function which updates it, the function must return back the new - pointer to the caller. This is the price of trying to do this in C. - - * The following are the only functions that are thread-safe on a single data - structure, i.e. can be run in multiple threads simultaneously on the same - data structure - hmlen shlen - hmlenu shlenu - hmget_ts shget_ts - hmgeti_ts shgeti_ts - hmgets_ts shgets_ts - - * You iterate over the contents of a dynamic array and a hashmap in exactly - the same way, using arrlen/hmlen/shlen: - - for (i=0; i < arrlen(foo); ++i) - ... foo[i] ... - - * All operations except arrins/arrdel are O(1) amortized, but individual - operations can be slow, so these data structures may not be suitable - for real time use. Dynamic arrays double in capacity as needed, so - elements are copied an average of once. Hash tables double/halve - their size as needed, with appropriate hysteresis to maintain O(1) - performance. - -NOTES - DYNAMIC ARRAY - - * If you know how long a dynamic array is going to be in advance, you can avoid - extra memory allocations by using arrsetlen to allocate it to that length in - advance and use foo[n] while filling it out, or arrsetcap to allocate the memory - for that length and use arrput/arrpush as normal. - - * Unlike some other versions of the dynamic array, this version should - be safe to use with strict-aliasing optimizations. - -NOTES - HASH MAP - - * For compilers other than GCC and clang (e.g. Visual Studio), for hmput/hmget/hmdel - and variants, the key must be an lvalue (so the macro can take the address of it). - Extensions are used that eliminate this requirement if you're using C99 and later - in GCC or clang, or if you're using C++ in GCC. But note that this can make your - code less portable. - - * To test for presence of a key in a hashmap, just do 'hmgeti(foo,key) >= 0'. - - * The iteration order of your data in the hashmap is determined solely by the - order of insertions and deletions. In particular, if you never delete, new - keys are always added at the end of the array. This will be consistent - across all platforms and versions of the library. However, you should not - attempt to serialize the internal hash table, as the hash is not consistent - between different platforms, and may change with future versions of the library. - - * Use sh_new_arena() for string hashmaps that you never delete from. Initialize - with NULL if you're managing the memory for your strings, or your strings are - never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup(). - @TODO: make an arena variant that garbage collects the strings with a trivial - copy collector into a new arena whenever the table shrinks / rebuilds. Since - current arena recommendation is to only use arena if it never deletes, then - this can just replace current arena implementation. - - * If adversarial input is a serious concern and you're on a 64-bit platform, - enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass - a strong random number to stbds_rand_seed. - - * The default value for the hash table is stored in foo[-1], so if you - use code like 'hmget(T,k)->value = 5' you can accidentally overwrite - the value stored by hmdefault if 'k' is not present. - -CREDITS - - Sean Barrett -- library, idea for dynamic array API/implementation - Per Vognsen -- idea for hash table API/implementation - Rafael Sachetto -- arrpop() - github:HeroicKatora -- arraddn() reworking - - Bugfixes: - Andy Durdin - Shane Liesegang - Vinh Truong - Andreas Molzer - github:hashitaku - github:srdjanstipic - Macoy Madson - Andreas Vennstrom - Tobias Mansfield-Williams -*/ - -#ifdef STBDS_UNIT_TESTS -#define _CRT_SECURE_NO_WARNINGS -#endif - -#ifndef INCLUDE_STB_DS_H -#define INCLUDE_STB_DS_H - -#include -#include - -#ifndef STBDS_NO_SHORT_NAMES -#define arrlen stbds_arrlen -#define arrlenu stbds_arrlenu -#define arrput stbds_arrput -#define arrpush stbds_arrput -#define arrpop stbds_arrpop -#define arrfree stbds_arrfree -#define arraddn stbds_arraddn // deprecated, use one of the following instead: -#define arraddnptr stbds_arraddnptr -#define arraddnindex stbds_arraddnindex -#define arrsetlen stbds_arrsetlen -#define arrlast stbds_arrlast -#define arrins stbds_arrins -#define arrinsn stbds_arrinsn -#define arrdel stbds_arrdel -#define arrdeln stbds_arrdeln -#define arrdelswap stbds_arrdelswap -#define arrcap stbds_arrcap -#define arrsetcap stbds_arrsetcap - -#define hmput stbds_hmput -#define hmputs stbds_hmputs -#define hmget stbds_hmget -#define hmget_ts stbds_hmget_ts -#define hmgets stbds_hmgets -#define hmgetp stbds_hmgetp -#define hmgetp_ts stbds_hmgetp_ts -#define hmgetp_null stbds_hmgetp_null -#define hmgeti stbds_hmgeti -#define hmgeti_ts stbds_hmgeti_ts -#define hmdel stbds_hmdel -#define hmlen stbds_hmlen -#define hmlenu stbds_hmlenu -#define hmfree stbds_hmfree -#define hmdefault stbds_hmdefault -#define hmdefaults stbds_hmdefaults - -#define shput stbds_shput -#define shputi stbds_shputi -#define shputs stbds_shputs -#define shget stbds_shget -#define shgeti stbds_shgeti -#define shgets stbds_shgets -#define shgetp stbds_shgetp -#define shgetp_null stbds_shgetp_null -#define shdel stbds_shdel -#define shlen stbds_shlen -#define shlenu stbds_shlenu -#define shfree stbds_shfree -#define shdefault stbds_shdefault -#define shdefaults stbds_shdefaults -#define sh_new_arena stbds_sh_new_arena -#define sh_new_strdup stbds_sh_new_strdup - -#define stralloc stbds_stralloc -#define strreset stbds_strreset -#endif - -#if defined(STBDS_REALLOC) && !defined(STBDS_FREE) || !defined(STBDS_REALLOC) && defined(STBDS_FREE) -#error "You must define both STBDS_REALLOC and STBDS_FREE, or neither." -#endif -#if !defined(STBDS_REALLOC) && !defined(STBDS_FREE) -#include -#define STBDS_REALLOC(c,p,s) realloc(p,s) -#define STBDS_FREE(c,p) free(p) -#endif - -#ifdef _MSC_VER -#define STBDS_NOTUSED(v) (void)(v) -#else -#define STBDS_NOTUSED(v) (void)sizeof(v) -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -// for security against attackers, seed the library with a random number, at least time() but stronger is better -extern void stbds_rand_seed(size_t seed); - -// these are the hash functions used internally if you want to test them or use them for other purposes -extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed); -extern size_t stbds_hash_string(char *str, size_t seed); - -// this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'. -typedef struct stbds_string_arena stbds_string_arena; -extern char * stbds_stralloc(stbds_string_arena *a, char *str); -extern void stbds_strreset(stbds_string_arena *a); - -// have to #define STBDS_UNIT_TESTS to call this -extern void stbds_unit_tests(void); - -/////////////// -// -// Everything below here is implementation details -// - -extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap); -extern void stbds_arrfreef(void *a); -extern void stbds_hmfree_func(void *p, size_t elemsize); -extern void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); -extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode); -extern void * stbds_hmput_default(void *a, size_t elemsize); -extern void * stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); -extern void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode); -extern void * stbds_shmode_func(size_t elemsize, int mode); - -#ifdef __cplusplus -} -#endif - -#if defined(__GNUC__) || defined(__clang__) -#define STBDS_HAS_TYPEOF -#ifdef __cplusplus -//#define STBDS_HAS_LITERAL_ARRAY // this is currently broken for clang -#endif -#endif - -#if !defined(__cplusplus) -#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L -#define STBDS_HAS_LITERAL_ARRAY -#endif -#endif - -// this macro takes the address of the argument, but on gcc/clang can accept rvalues -#if defined(STBDS_HAS_LITERAL_ARRAY) && defined(STBDS_HAS_TYPEOF) - #if __clang__ - #define STBDS_ADDRESSOF(typevar, value) ((__typeof__(typevar)[1]){value}) // literal array decays to pointer to value - #else - #define STBDS_ADDRESSOF(typevar, value) ((typeof(typevar)[1]){value}) // literal array decays to pointer to value - #endif -#else -#define STBDS_ADDRESSOF(typevar, value) &(value) -#endif - -#define STBDS_OFFSETOF(var,field) ((char *) &(var)->field - (char *) (var)) - -#define stbds_header(t) ((stbds_array_header *) (t) - 1) -#define stbds_temp(t) stbds_header(t)->temp -#define stbds_temp_key(t) (*(char **) stbds_header(t)->hash_table) - -#define stbds_arrsetcap(a,n) (stbds_arrgrow(a,0,n)) -#define stbds_arrsetlen(a,n) ((stbds_arrcap(a) < (size_t) (n) ? stbds_arrsetcap((a),(size_t)(n)),0 : 0), (a) ? stbds_header(a)->length = (size_t) (n) : 0) -#define stbds_arrcap(a) ((a) ? stbds_header(a)->capacity : 0) -#define stbds_arrlen(a) ((a) ? (ptrdiff_t) stbds_header(a)->length : 0) -#define stbds_arrlenu(a) ((a) ? stbds_header(a)->length : 0) -#define stbds_arrput(a,v) (stbds_arrmaybegrow(a,1), (a)[stbds_header(a)->length++] = (v)) -#define stbds_arrpush stbds_arrput // synonym -#define stbds_arrpop(a) (stbds_header(a)->length--, (a)[stbds_header(a)->length]) -#define stbds_arraddn(a,n) ((void)(stbds_arraddnindex(a, n))) // deprecated, use one of the following instead: -#define stbds_arraddnptr(a,n) (stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), &(a)[stbds_header(a)->length-(n)]) : (a)) -#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a)) -#define stbds_arraddnoff stbds_arraddnindex -#define stbds_arrlast(a) ((a)[stbds_header(a)->length-1]) -#define stbds_arrfree(a) ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL) -#define stbds_arrdel(a,i) stbds_arrdeln(a,i,1) -#define stbds_arrdeln(a,i,n) (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n)) -#define stbds_arrdelswap(a,i) ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1) -#define stbds_arrinsn(a,i,n) (stbds_arraddn((a),(n)), memmove(&(a)[(i)+(n)], &(a)[i], sizeof *(a) * (stbds_header(a)->length-(n)-(i)))) -#define stbds_arrins(a,i,v) (stbds_arrinsn((a),(i),1), (a)[i]=(v)) - -#define stbds_arrmaybegrow(a,n) ((!(a) || stbds_header(a)->length + (n) > stbds_header(a)->capacity) \ - ? (stbds_arrgrow(a,n,0),0) : 0) - -#define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c))) - -#define stbds_hmput(t, k, v) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \ - (t)[stbds_temp((t)-1)].key = (k), \ - (t)[stbds_temp((t)-1)].value = (v)) - -#define stbds_hmputs(t, s) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \ - (t)[stbds_temp((t)-1)] = (s)) - -#define stbds_hmgeti(t,k) \ - ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \ - stbds_temp((t)-1)) - -#define stbds_hmgeti_ts(t,k,temp) \ - ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \ - (temp)) - -#define stbds_hmgetp(t, k) \ - ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)]) - -#define stbds_hmgetp_ts(t, k, temp) \ - ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp]) - -#define stbds_hmdel(t,k) \ - (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0) - -#define stbds_hmdefault(t, v) \ - ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v)) - -#define stbds_hmdefaults(t, s) \ - ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s)) - -#define stbds_hmfree(p) \ - ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL) - -#define stbds_hmgets(t, k) (*stbds_hmgetp(t,k)) -#define stbds_hmget(t, k) (stbds_hmgetp(t,k)->value) -#define stbds_hmget_ts(t, k, temp) (stbds_hmgetp_ts(t,k,temp)->value) -#define stbds_hmlen(t) ((t) ? (ptrdiff_t) stbds_header((t)-1)->length-1 : 0) -#define stbds_hmlenu(t) ((t) ? stbds_header((t)-1)->length-1 : 0) -#define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) - -#define stbds_shput(t, k, v) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ - (t)[stbds_temp((t)-1)].value = (v)) - -#define stbds_shputi(t, k, v) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ - (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1)) - -#define stbds_shputs(t, s) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \ - (t)[stbds_temp((t)-1)] = (s), \ - (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally - -#define stbds_pshput(t, p) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \ - (t)[stbds_temp((t)-1)] = (p)) - -#define stbds_shgeti(t,k) \ - ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ - stbds_temp((t)-1)) - -#define stbds_pshgeti(t,k) \ - ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \ - stbds_temp((t)-1)) - -#define stbds_shgetp(t, k) \ - ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)]) - -#define stbds_pshget(t, k) \ - ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)]) - -#define stbds_shdel(t,k) \ - (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0) -#define stbds_pshdel(t,k) \ - (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0) - -#define stbds_sh_new_arena(t) \ - ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA)) -#define stbds_sh_new_strdup(t) \ - ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP)) - -#define stbds_shdefault(t, v) stbds_hmdefault(t,v) -#define stbds_shdefaults(t, s) stbds_hmdefaults(t,s) - -#define stbds_shfree stbds_hmfree -#define stbds_shlenu stbds_hmlenu - -#define stbds_shgets(t, k) (*stbds_shgetp(t,k)) -#define stbds_shget(t, k) (stbds_shgetp(t,k)->value) -#define stbds_shgetp_null(t,k) (stbds_shgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) -#define stbds_shlen stbds_hmlen - -typedef struct -{ - size_t length; - size_t capacity; - void * hash_table; - ptrdiff_t temp; -} stbds_array_header; - -typedef struct stbds_string_block -{ - struct stbds_string_block *next; - char storage[8]; -} stbds_string_block; - -struct stbds_string_arena -{ - stbds_string_block *storage; - size_t remaining; - unsigned char block; - unsigned char mode; // this isn't used by the string arena itself -}; - -#define STBDS_HM_BINARY 0 -#define STBDS_HM_STRING 1 - -enum -{ - STBDS_SH_NONE, - STBDS_SH_DEFAULT, - STBDS_SH_STRDUP, - STBDS_SH_ARENA -}; - -#ifdef __cplusplus -// in C we use implicit assignment from these void*-returning functions to T*. -// in C++ these templates make the same code work -template static T * stbds_arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) { - return (T*)stbds_arrgrowf((void *)a, elemsize, addlen, min_cap); -} -template static T * stbds_hmget_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { - return (T*)stbds_hmget_key((void*)a, elemsize, key, keysize, mode); -} -template static T * stbds_hmget_key_ts_wrapper(T *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) { - return (T*)stbds_hmget_key_ts((void*)a, elemsize, key, keysize, temp, mode); -} -template static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) { - return (T*)stbds_hmput_default((void *)a, elemsize); -} -template static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { - return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode); -} -template static T * stbds_hmdel_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode){ - return (T*)stbds_hmdel_key((void*)a, elemsize, key, keysize, keyoffset, mode); -} -template static T * stbds_shmode_func_wrapper(T *, size_t elemsize, int mode) { - return (T*)stbds_shmode_func(elemsize, mode); -} -#else -#define stbds_arrgrowf_wrapper stbds_arrgrowf -#define stbds_hmget_key_wrapper stbds_hmget_key -#define stbds_hmget_key_ts_wrapper stbds_hmget_key_ts -#define stbds_hmput_default_wrapper stbds_hmput_default -#define stbds_hmput_key_wrapper stbds_hmput_key -#define stbds_hmdel_key_wrapper stbds_hmdel_key -#define stbds_shmode_func_wrapper(t,e,m) stbds_shmode_func(e,m) -#endif - -#endif // INCLUDE_STB_DS_H - - -////////////////////////////////////////////////////////////////////////////// -// -// IMPLEMENTATION -// - -#ifdef STB_DS_IMPLEMENTATION -#include -#include - -#ifndef STBDS_ASSERT -#define STBDS_ASSERT_WAS_UNDEFINED -#define STBDS_ASSERT(x) ((void) 0) -#endif - -#ifdef STBDS_STATISTICS -#define STBDS_STATS(x) x -size_t stbds_array_grow; -size_t stbds_hash_grow; -size_t stbds_hash_shrink; -size_t stbds_hash_rebuild; -size_t stbds_hash_probes; -size_t stbds_hash_alloc; -size_t stbds_rehash_probes; -size_t stbds_rehash_items; -#else -#define STBDS_STATS(x) -#endif - -// -// stbds_arr implementation -// - -//int *prev_allocs[65536]; -//int num_prev; - -void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap) -{ - stbds_array_header temp={0}; // force debugging - void *b; - size_t min_len = stbds_arrlen(a) + addlen; - (void) sizeof(temp); - - // compute the minimum capacity needed - if (min_len > min_cap) - min_cap = min_len; - - if (min_cap <= stbds_arrcap(a)) - return a; - - // increase needed capacity to guarantee O(1) amortized - if (min_cap < 2 * stbds_arrcap(a)) - min_cap = 2 * stbds_arrcap(a); - else if (min_cap < 4) - min_cap = 4; - - //if (num_prev < 65536) if (a) prev_allocs[num_prev++] = (int *) ((char *) a+1); - //if (num_prev == 2201) - // num_prev = num_prev; - b = STBDS_REALLOC(NULL, (a) ? stbds_header(a) : 0, elemsize * min_cap + sizeof(stbds_array_header)); - //if (num_prev < 65536) prev_allocs[num_prev++] = (int *) (char *) b; - b = (char *) b + sizeof(stbds_array_header); - if (a == NULL) { - stbds_header(b)->length = 0; - stbds_header(b)->hash_table = 0; - stbds_header(b)->temp = 0; - } else { - STBDS_STATS(++stbds_array_grow); - } - stbds_header(b)->capacity = min_cap; - - return b; -} - -void stbds_arrfreef(void *a) -{ - STBDS_FREE(NULL, stbds_header(a)); -} - -// -// stbds_hm hash table implementation -// - -#ifdef STBDS_INTERNAL_SMALL_BUCKET -#define STBDS_BUCKET_LENGTH 4 -#else -#define STBDS_BUCKET_LENGTH 8 -#endif - -#define STBDS_BUCKET_SHIFT (STBDS_BUCKET_LENGTH == 8 ? 3 : 2) -#define STBDS_BUCKET_MASK (STBDS_BUCKET_LENGTH-1) -#define STBDS_CACHE_LINE_SIZE 64 - -#define STBDS_ALIGN_FWD(n,a) (((n) + (a) - 1) & ~((a)-1)) - -typedef struct -{ - size_t hash [STBDS_BUCKET_LENGTH]; - ptrdiff_t index[STBDS_BUCKET_LENGTH]; -} stbds_hash_bucket; // in 32-bit, this is one 64-byte cache line; in 64-bit, each array is one 64-byte cache line - -typedef struct -{ - char * temp_key; // this MUST be the first field of the hash table - size_t slot_count; - size_t used_count; - size_t used_count_threshold; - size_t used_count_shrink_threshold; - size_t tombstone_count; - size_t tombstone_count_threshold; - size_t seed; - size_t slot_count_log2; - stbds_string_arena string; - stbds_hash_bucket *storage; // not a separate allocation, just 64-byte aligned storage after this struct -} stbds_hash_index; - -#define STBDS_INDEX_EMPTY -1 -#define STBDS_INDEX_DELETED -2 -#define STBDS_INDEX_IN_USE(x) ((x) >= 0) - -#define STBDS_HASH_EMPTY 0 -#define STBDS_HASH_DELETED 1 - -static size_t stbds_hash_seed=0x31415926; - -void stbds_rand_seed(size_t seed) -{ - stbds_hash_seed = seed; -} - -#define stbds_load_32_or_64(var, temp, v32, v64_hi, v64_lo) \ - temp = v64_lo ^ v32, temp <<= 16, temp <<= 16, temp >>= 16, temp >>= 16, /* discard if 32-bit */ \ - var = v64_hi, var <<= 16, var <<= 16, /* discard if 32-bit */ \ - var ^= temp ^ v32 - -#define STBDS_SIZE_T_BITS ((sizeof (size_t)) * 8) - -static size_t stbds_probe_position(size_t hash, size_t slot_count, size_t slot_log2) -{ - size_t pos; - STBDS_NOTUSED(slot_log2); - pos = hash & (slot_count-1); - #ifdef STBDS_INTERNAL_BUCKET_START - pos &= ~STBDS_BUCKET_MASK; - #endif - return pos; -} - -static size_t stbds_log2(size_t slot_count) -{ - size_t n=0; - while (slot_count > 1) { - slot_count >>= 1; - ++n; - } - return n; -} - -static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_index *ot) -{ - stbds_hash_index *t; - t = (stbds_hash_index *) STBDS_REALLOC(NULL,0,(slot_count >> STBDS_BUCKET_SHIFT) * sizeof(stbds_hash_bucket) + sizeof(stbds_hash_index) + STBDS_CACHE_LINE_SIZE-1); - t->storage = (stbds_hash_bucket *) STBDS_ALIGN_FWD((size_t) (t+1), STBDS_CACHE_LINE_SIZE); - t->slot_count = slot_count; - t->slot_count_log2 = stbds_log2(slot_count); - t->tombstone_count = 0; - t->used_count = 0; - - #if 0 // A1 - t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow - t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild - t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink - #elif 1 // A2 - //t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow - //t->tombstone_count_threshold = slot_count* 3/16; // if tombstones are 3/16th of table, rebuild - //t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink - - // compute without overflowing - t->used_count_threshold = slot_count - (slot_count>>2); - t->tombstone_count_threshold = (slot_count>>3) + (slot_count>>4); - t->used_count_shrink_threshold = slot_count >> 2; - - #elif 0 // B1 - t->used_count_threshold = slot_count*13/16; // if 13/16th of table is occupied, grow - t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild - t->used_count_shrink_threshold = slot_count* 5/16; // if table is only 5/16th full, shrink - #else // C1 - t->used_count_threshold = slot_count*14/16; // if 14/16th of table is occupied, grow - t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild - t->used_count_shrink_threshold = slot_count* 6/16; // if table is only 6/16th full, shrink - #endif - // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 - // Note that the larger tables have high variance as they were run fewer times - // A1 A2 B1 C1 - // 0.10ms : 0.10ms : 0.10ms : 0.11ms : 2,000 inserts creating 2K table - // 0.96ms : 0.95ms : 0.97ms : 1.04ms : 20,000 inserts creating 20K table - // 14.48ms : 14.46ms : 10.63ms : 11.00ms : 200,000 inserts creating 200K table - // 195.74ms : 196.35ms : 203.69ms : 214.92ms : 2,000,000 inserts creating 2M table - // 2193.88ms : 2209.22ms : 2285.54ms : 2437.17ms : 20,000,000 inserts creating 20M table - // 65.27ms : 53.77ms : 65.33ms : 65.47ms : 500,000 inserts & deletes in 2K table - // 72.78ms : 62.45ms : 71.95ms : 72.85ms : 500,000 inserts & deletes in 20K table - // 89.47ms : 77.72ms : 96.49ms : 96.75ms : 500,000 inserts & deletes in 200K table - // 97.58ms : 98.14ms : 97.18ms : 97.53ms : 500,000 inserts & deletes in 2M table - // 118.61ms : 119.62ms : 120.16ms : 118.86ms : 500,000 inserts & deletes in 20M table - // 192.11ms : 194.39ms : 196.38ms : 195.73ms : 500,000 inserts & deletes in 200M table - - if (slot_count <= STBDS_BUCKET_LENGTH) - t->used_count_shrink_threshold = 0; - // to avoid infinite loop, we need to guarantee that at least one slot is empty and will terminate probes - STBDS_ASSERT(t->used_count_threshold + t->tombstone_count_threshold < t->slot_count); - STBDS_STATS(++stbds_hash_alloc); - if (ot) { - t->string = ot->string; - // reuse old seed so we can reuse old hashes so below "copy out old data" doesn't do any hashing - t->seed = ot->seed; - } else { - size_t a,b,temp; - memset(&t->string, 0, sizeof(t->string)); - t->seed = stbds_hash_seed; - // LCG - // in 32-bit, a = 2147001325 b = 715136305 - // in 64-bit, a = 2862933555777941757 b = 3037000493 - stbds_load_32_or_64(a,temp, 2147001325, 0x27bb2ee6, 0x87b0b0fd); - stbds_load_32_or_64(b,temp, 715136305, 0, 0xb504f32d); - stbds_hash_seed = stbds_hash_seed * a + b; - } - - { - size_t i,j; - for (i=0; i < slot_count >> STBDS_BUCKET_SHIFT; ++i) { - stbds_hash_bucket *b = &t->storage[i]; - for (j=0; j < STBDS_BUCKET_LENGTH; ++j) - b->hash[j] = STBDS_HASH_EMPTY; - for (j=0; j < STBDS_BUCKET_LENGTH; ++j) - b->index[j] = STBDS_INDEX_EMPTY; - } - } - - // copy out the old data, if any - if (ot) { - size_t i,j; - t->used_count = ot->used_count; - for (i=0; i < ot->slot_count >> STBDS_BUCKET_SHIFT; ++i) { - stbds_hash_bucket *ob = &ot->storage[i]; - for (j=0; j < STBDS_BUCKET_LENGTH; ++j) { - if (STBDS_INDEX_IN_USE(ob->index[j])) { - size_t hash = ob->hash[j]; - size_t pos = stbds_probe_position(hash, t->slot_count, t->slot_count_log2); - size_t step = STBDS_BUCKET_LENGTH; - STBDS_STATS(++stbds_rehash_items); - for (;;) { - size_t limit,z; - stbds_hash_bucket *bucket; - bucket = &t->storage[pos >> STBDS_BUCKET_SHIFT]; - STBDS_STATS(++stbds_rehash_probes); - - for (z=pos & STBDS_BUCKET_MASK; z < STBDS_BUCKET_LENGTH; ++z) { - if (bucket->hash[z] == 0) { - bucket->hash[z] = hash; - bucket->index[z] = ob->index[j]; - goto done; - } - } - - limit = pos & STBDS_BUCKET_MASK; - for (z = 0; z < limit; ++z) { - if (bucket->hash[z] == 0) { - bucket->hash[z] = hash; - bucket->index[z] = ob->index[j]; - goto done; - } - } - - pos += step; // quadratic probing - step += STBDS_BUCKET_LENGTH; - pos &= (t->slot_count-1); - } - } - done: - ; - } - } - } - - return t; -} - -#define STBDS_ROTATE_LEFT(val, n) (((val) << (n)) | ((val) >> (STBDS_SIZE_T_BITS - (n)))) -#define STBDS_ROTATE_RIGHT(val, n) (((val) >> (n)) | ((val) << (STBDS_SIZE_T_BITS - (n)))) - -size_t stbds_hash_string(char *str, size_t seed) -{ - size_t hash = seed; - while (*str) - hash = STBDS_ROTATE_LEFT(hash, 9) + (unsigned char) *str++; - - // Thomas Wang 64-to-32 bit mix function, hopefully also works in 32 bits - hash ^= seed; - hash = (~hash) + (hash << 18); - hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,31); - hash = hash * 21; - hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,11); - hash += (hash << 6); - hash ^= STBDS_ROTATE_RIGHT(hash,22); - return hash+seed; -} - -#ifdef STBDS_SIPHASH_2_4 -#define STBDS_SIPHASH_C_ROUNDS 2 -#define STBDS_SIPHASH_D_ROUNDS 4 -typedef int STBDS_SIPHASH_2_4_can_only_be_used_in_64_bit_builds[sizeof(size_t) == 8 ? 1 : -1]; -#endif - -#ifndef STBDS_SIPHASH_C_ROUNDS -#define STBDS_SIPHASH_C_ROUNDS 1 -#endif -#ifndef STBDS_SIPHASH_D_ROUNDS -#define STBDS_SIPHASH_D_ROUNDS 1 -#endif - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable:4127) // conditional expression is constant, for do..while(0) and sizeof()== -#endif - -static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed) -{ - unsigned char *d = (unsigned char *) p; - size_t i,j; - size_t v0,v1,v2,v3, data; - - // hash that works on 32- or 64-bit registers without knowing which we have - // (computes different results on 32-bit and 64-bit platform) - // derived from siphash, but on 32-bit platforms very different as it uses 4 32-bit state not 4 64-bit - v0 = ((((size_t) 0x736f6d65 << 16) << 16) + 0x70736575) ^ seed; - v1 = ((((size_t) 0x646f7261 << 16) << 16) + 0x6e646f6d) ^ ~seed; - v2 = ((((size_t) 0x6c796765 << 16) << 16) + 0x6e657261) ^ seed; - v3 = ((((size_t) 0x74656462 << 16) << 16) + 0x79746573) ^ ~seed; - - #ifdef STBDS_TEST_SIPHASH_2_4 - // hardcoded with key material in the siphash test vectors - v0 ^= 0x0706050403020100ull ^ seed; - v1 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; - v2 ^= 0x0706050403020100ull ^ seed; - v3 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; - #endif - - #define STBDS_SIPROUND() \ - do { \ - v0 += v1; v1 = STBDS_ROTATE_LEFT(v1, 13); v1 ^= v0; v0 = STBDS_ROTATE_LEFT(v0,STBDS_SIZE_T_BITS/2); \ - v2 += v3; v3 = STBDS_ROTATE_LEFT(v3, 16); v3 ^= v2; \ - v2 += v1; v1 = STBDS_ROTATE_LEFT(v1, 17); v1 ^= v2; v2 = STBDS_ROTATE_LEFT(v2,STBDS_SIZE_T_BITS/2); \ - v0 += v3; v3 = STBDS_ROTATE_LEFT(v3, 21); v3 ^= v0; \ - } while (0) - - for (i=0; i+sizeof(size_t) <= len; i += sizeof(size_t), d += sizeof(size_t)) { - data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); - data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4 - - v3 ^= data; - for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) - STBDS_SIPROUND(); - v0 ^= data; - } - data = len << (STBDS_SIZE_T_BITS-8); - switch (len - i) { - case 7: data |= ((size_t) d[6] << 24) << 24; // fall through - case 6: data |= ((size_t) d[5] << 20) << 20; // fall through - case 5: data |= ((size_t) d[4] << 16) << 16; // fall through - case 4: data |= (d[3] << 24); // fall through - case 3: data |= (d[2] << 16); // fall through - case 2: data |= (d[1] << 8); // fall through - case 1: data |= d[0]; // fall through - case 0: break; - } - v3 ^= data; - for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) - STBDS_SIPROUND(); - v0 ^= data; - v2 ^= 0xff; - for (j=0; j < STBDS_SIPHASH_D_ROUNDS; ++j) - STBDS_SIPROUND(); - -#ifdef STBDS_SIPHASH_2_4 - return v0^v1^v2^v3; -#else - return v1^v2^v3; // slightly stronger since v0^v3 in above cancels out final round operation? I tweeted at the authors of SipHash about this but they didn't reply -#endif -} - -size_t stbds_hash_bytes(void *p, size_t len, size_t seed) -{ -#ifdef STBDS_SIPHASH_2_4 - return stbds_siphash_bytes(p,len,seed); -#else - unsigned char *d = (unsigned char *) p; - - if (len == 4) { - unsigned int hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); - #if 0 - // HASH32-A Bob Jenkin's hash function w/o large constants - hash ^= seed; - hash -= (hash<<6); - hash ^= (hash>>17); - hash -= (hash<<9); - hash ^= seed; - hash ^= (hash<<4); - hash -= (hash<<3); - hash ^= (hash<<10); - hash ^= (hash>>15); - #elif 1 - // HASH32-BB Bob Jenkin's presumably-accidental version of Thomas Wang hash with rotates turned into shifts. - // Note that converting these back to rotates makes it run a lot slower, presumably due to collisions, so I'm - // not really sure what's going on. - hash ^= seed; - hash = (hash ^ 61) ^ (hash >> 16); - hash = hash + (hash << 3); - hash = hash ^ (hash >> 4); - hash = hash * 0x27d4eb2d; - hash ^= seed; - hash = hash ^ (hash >> 15); - #else // HASH32-C - Murmur3 - hash ^= seed; - hash *= 0xcc9e2d51; - hash = (hash << 17) | (hash >> 15); - hash *= 0x1b873593; - hash ^= seed; - hash = (hash << 19) | (hash >> 13); - hash = hash*5 + 0xe6546b64; - hash ^= hash >> 16; - hash *= 0x85ebca6b; - hash ^= seed; - hash ^= hash >> 13; - hash *= 0xc2b2ae35; - hash ^= hash >> 16; - #endif - // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 - // Note that the larger tables have high variance as they were run fewer times - // HASH32-A // HASH32-BB // HASH32-C - // 0.10ms // 0.10ms // 0.10ms : 2,000 inserts creating 2K table - // 0.96ms // 0.95ms // 0.99ms : 20,000 inserts creating 20K table - // 14.69ms // 14.43ms // 14.97ms : 200,000 inserts creating 200K table - // 199.99ms // 195.36ms // 202.05ms : 2,000,000 inserts creating 2M table - // 2234.84ms // 2187.74ms // 2240.38ms : 20,000,000 inserts creating 20M table - // 55.68ms // 53.72ms // 57.31ms : 500,000 inserts & deletes in 2K table - // 63.43ms // 61.99ms // 65.73ms : 500,000 inserts & deletes in 20K table - // 80.04ms // 77.96ms // 81.83ms : 500,000 inserts & deletes in 200K table - // 100.42ms // 97.40ms // 102.39ms : 500,000 inserts & deletes in 2M table - // 119.71ms // 120.59ms // 121.63ms : 500,000 inserts & deletes in 20M table - // 185.28ms // 195.15ms // 187.74ms : 500,000 inserts & deletes in 200M table - // 15.58ms // 14.79ms // 15.52ms : 200,000 inserts creating 200K table with varying key spacing - - return (((size_t) hash << 16 << 16) | hash) ^ seed; - } else if (len == 8 && sizeof(size_t) == 8) { - size_t hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); - hash |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // avoid warning if size_t == 4 - hash ^= seed; - hash = (~hash) + (hash << 21); - hash ^= STBDS_ROTATE_RIGHT(hash,24); - hash *= 265; - hash ^= STBDS_ROTATE_RIGHT(hash,14); - hash ^= seed; - hash *= 21; - hash ^= STBDS_ROTATE_RIGHT(hash,28); - hash += (hash << 31); - hash = (~hash) + (hash << 18); - return hash; - } else { - return stbds_siphash_bytes(p,len,seed); - } -#endif -} -#ifdef _MSC_VER -#pragma warning(pop) -#endif - - -static int stbds_is_key_equal(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode, size_t i) -{ - if (mode >= STBDS_HM_STRING) - return 0==strcmp((char *) key, * (char **) ((char *) a + elemsize*i + keyoffset)); - else - return 0==memcmp(key, (char *) a + elemsize*i + keyoffset, keysize); -} - -#define STBDS_HASH_TO_ARR(x,elemsize) ((char*) (x) - (elemsize)) -#define STBDS_ARR_TO_HASH(x,elemsize) ((char*) (x) + (elemsize)) - -#define stbds_hash_table(a) ((stbds_hash_index *) stbds_header(a)->hash_table) - -void stbds_hmfree_func(void *a, size_t elemsize) -{ - if (a == NULL) return; - if (stbds_hash_table(a) != NULL) { - if (stbds_hash_table(a)->string.mode == STBDS_SH_STRDUP) { - size_t i; - // skip 0th element, which is default - for (i=1; i < stbds_header(a)->length; ++i) - STBDS_FREE(NULL, *(char**) ((char *) a + elemsize*i)); - } - stbds_strreset(&stbds_hash_table(a)->string); - } - STBDS_FREE(NULL, stbds_header(a)->hash_table); - STBDS_FREE(NULL, stbds_header(a)); -} - -static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) -{ - void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); - stbds_hash_index *table = stbds_hash_table(raw_a); - size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); - size_t step = STBDS_BUCKET_LENGTH; - size_t limit,i; - size_t pos; - stbds_hash_bucket *bucket; - - if (hash < 2) hash += 2; // stored hash values are forbidden from being 0, so we can detect empty slots - - pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); - - for (;;) { - STBDS_STATS(++stbds_hash_probes); - bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; - - // start searching from pos to end of bucket, this should help performance on small hash tables that fit in cache - for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { - if (bucket->hash[i] == hash) { - if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { - return (pos & ~STBDS_BUCKET_MASK)+i; - } - } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { - return -1; - } - } - - // search from beginning of bucket to pos - limit = pos & STBDS_BUCKET_MASK; - for (i = 0; i < limit; ++i) { - if (bucket->hash[i] == hash) { - if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { - return (pos & ~STBDS_BUCKET_MASK)+i; - } - } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { - return -1; - } - } - - // quadratic probing - pos += step; - step += STBDS_BUCKET_LENGTH; - pos &= (table->slot_count-1); - } - /* NOTREACHED */ -} - -void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) -{ - size_t keyoffset = 0; - if (a == NULL) { - // make it non-empty so we can return a temp - a = stbds_arrgrowf(0, elemsize, 0, 1); - stbds_header(a)->length += 1; - memset(a, 0, elemsize); - *temp = STBDS_INDEX_EMPTY; - // adjust a to point after the default element - return STBDS_ARR_TO_HASH(a,elemsize); - } else { - stbds_hash_index *table; - void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); - // adjust a to point to the default element - table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; - if (table == 0) { - *temp = -1; - } else { - ptrdiff_t slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); - if (slot < 0) { - *temp = STBDS_INDEX_EMPTY; - } else { - stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; - *temp = b->index[slot & STBDS_BUCKET_MASK]; - } - } - return a; - } -} - -void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) -{ - ptrdiff_t temp; - void *p = stbds_hmget_key_ts(a, elemsize, key, keysize, &temp, mode); - stbds_temp(STBDS_HASH_TO_ARR(p,elemsize)) = temp; - return p; -} - -void * stbds_hmput_default(void *a, size_t elemsize) -{ - // three cases: - // a is NULL <- allocate - // a has a hash table but no entries, because of shmode <- grow - // a has entries <- do nothing - if (a == NULL || stbds_header(STBDS_HASH_TO_ARR(a,elemsize))->length == 0) { - a = stbds_arrgrowf(a ? STBDS_HASH_TO_ARR(a,elemsize) : NULL, elemsize, 0, 1); - stbds_header(a)->length += 1; - memset(a, 0, elemsize); - a=STBDS_ARR_TO_HASH(a,elemsize); - } - return a; -} - -static char *stbds_strdup(char *str); - -void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) -{ - size_t keyoffset=0; - void *raw_a; - stbds_hash_index *table; - - if (a == NULL) { - a = stbds_arrgrowf(0, elemsize, 0, 1); - memset(a, 0, elemsize); - stbds_header(a)->length += 1; - // adjust a to point AFTER the default element - a = STBDS_ARR_TO_HASH(a,elemsize); - } - - // adjust a to point to the default element - raw_a = a; - a = STBDS_HASH_TO_ARR(a,elemsize); - - table = (stbds_hash_index *) stbds_header(a)->hash_table; - - if (table == NULL || table->used_count >= table->used_count_threshold) { - stbds_hash_index *nt; - size_t slot_count; - - slot_count = (table == NULL) ? STBDS_BUCKET_LENGTH : table->slot_count*2; - nt = stbds_make_hash_index(slot_count, table); - if (table) - STBDS_FREE(NULL, table); - else - nt->string.mode = mode >= STBDS_HM_STRING ? STBDS_SH_DEFAULT : 0; - stbds_header(a)->hash_table = table = nt; - STBDS_STATS(++stbds_hash_grow); - } - - // we iterate hash table explicitly because we want to track if we saw a tombstone - { - size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); - size_t step = STBDS_BUCKET_LENGTH; - size_t pos; - ptrdiff_t tombstone = -1; - stbds_hash_bucket *bucket; - - // stored hash values are forbidden from being 0, so we can detect empty slots to early out quickly - if (hash < 2) hash += 2; - - pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); - - for (;;) { - size_t limit, i; - STBDS_STATS(++stbds_hash_probes); - bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; - - // start searching from pos to end of bucket - for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { - if (bucket->hash[i] == hash) { - if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { - stbds_temp(a) = bucket->index[i]; - if (mode >= STBDS_HM_STRING) - stbds_temp_key(a) = * (char **) ((char *) raw_a + elemsize*bucket->index[i] + keyoffset); - return STBDS_ARR_TO_HASH(a,elemsize); - } - } else if (bucket->hash[i] == 0) { - pos = (pos & ~STBDS_BUCKET_MASK) + i; - goto found_empty_slot; - } else if (tombstone < 0) { - if (bucket->index[i] == STBDS_INDEX_DELETED) - tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); - } - } - - // search from beginning of bucket to pos - limit = pos & STBDS_BUCKET_MASK; - for (i = 0; i < limit; ++i) { - if (bucket->hash[i] == hash) { - if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { - stbds_temp(a) = bucket->index[i]; - return STBDS_ARR_TO_HASH(a,elemsize); - } - } else if (bucket->hash[i] == 0) { - pos = (pos & ~STBDS_BUCKET_MASK) + i; - goto found_empty_slot; - } else if (tombstone < 0) { - if (bucket->index[i] == STBDS_INDEX_DELETED) - tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); - } - } - - // quadratic probing - pos += step; - step += STBDS_BUCKET_LENGTH; - pos &= (table->slot_count-1); - } - found_empty_slot: - if (tombstone >= 0) { - pos = tombstone; - --table->tombstone_count; - } - ++table->used_count; - - { - ptrdiff_t i = (ptrdiff_t) stbds_arrlen(a); - // we want to do stbds_arraddn(1), but we can't use the macros since we don't have something of the right type - if ((size_t) i+1 > stbds_arrcap(a)) - *(void **) &a = stbds_arrgrowf(a, elemsize, 1, 0); - raw_a = STBDS_ARR_TO_HASH(a,elemsize); - - STBDS_ASSERT((size_t) i+1 <= stbds_arrcap(a)); - stbds_header(a)->length = i+1; - bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; - bucket->hash[pos & STBDS_BUCKET_MASK] = hash; - bucket->index[pos & STBDS_BUCKET_MASK] = i-1; - stbds_temp(a) = i-1; - - switch (table->string.mode) { - case STBDS_SH_STRDUP: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_strdup((char*) key); break; - case STBDS_SH_ARENA: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_stralloc(&table->string, (char*)key); break; - case STBDS_SH_DEFAULT: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = (char *) key; break; - default: memcpy((char *) a + elemsize*i, key, keysize); break; - } - } - return STBDS_ARR_TO_HASH(a,elemsize); - } -} - -void * stbds_shmode_func(size_t elemsize, int mode) -{ - void *a = stbds_arrgrowf(0, elemsize, 0, 1); - stbds_hash_index *h; - memset(a, 0, elemsize); - stbds_header(a)->length = 1; - stbds_header(a)->hash_table = h = (stbds_hash_index *) stbds_make_hash_index(STBDS_BUCKET_LENGTH, NULL); - h->string.mode = (unsigned char) mode; - return STBDS_ARR_TO_HASH(a,elemsize); -} - -void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) -{ - if (a == NULL) { - return 0; - } else { - stbds_hash_index *table; - void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); - table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; - stbds_temp(raw_a) = 0; - if (table == 0) { - return a; - } else { - ptrdiff_t slot; - slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); - if (slot < 0) - return a; - else { - stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; - int i = slot & STBDS_BUCKET_MASK; - ptrdiff_t old_index = b->index[i]; - ptrdiff_t final_index = (ptrdiff_t) stbds_arrlen(raw_a)-1-1; // minus one for the raw_a vs a, and minus one for 'last' - STBDS_ASSERT(slot < (ptrdiff_t) table->slot_count); - --table->used_count; - ++table->tombstone_count; - stbds_temp(raw_a) = 1; - STBDS_ASSERT(table->used_count >= 0); - //STBDS_ASSERT(table->tombstone_count < table->slot_count/4); - b->hash[i] = STBDS_HASH_DELETED; - b->index[i] = STBDS_INDEX_DELETED; - - if (mode == STBDS_HM_STRING && table->string.mode == STBDS_SH_STRDUP) - STBDS_FREE(NULL, *(char**) ((char *) a+elemsize*old_index)); - - // if indices are the same, memcpy is a no-op, but back-pointer-fixup will fail, so skip - if (old_index != final_index) { - // swap delete - memmove((char*) a + elemsize*old_index, (char*) a + elemsize*final_index, elemsize); - - // now find the slot for the last element - if (mode == STBDS_HM_STRING) - slot = stbds_hm_find_slot(a, elemsize, *(char**) ((char *) a+elemsize*old_index + keyoffset), keysize, keyoffset, mode); - else - slot = stbds_hm_find_slot(a, elemsize, (char* ) a+elemsize*old_index + keyoffset, keysize, keyoffset, mode); - STBDS_ASSERT(slot >= 0); - b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; - i = slot & STBDS_BUCKET_MASK; - STBDS_ASSERT(b->index[i] == final_index); - b->index[i] = old_index; - } - stbds_header(raw_a)->length -= 1; - - if (table->used_count < table->used_count_shrink_threshold && table->slot_count > STBDS_BUCKET_LENGTH) { - stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count>>1, table); - STBDS_FREE(NULL, table); - STBDS_STATS(++stbds_hash_shrink); - } else if (table->tombstone_count > table->tombstone_count_threshold) { - stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count , table); - STBDS_FREE(NULL, table); - STBDS_STATS(++stbds_hash_rebuild); - } - - return a; - } - } - } - /* NOTREACHED */ -} - -static char *stbds_strdup(char *str) -{ - // to keep replaceable allocator simple, we don't want to use strdup. - // rolling our own also avoids problem of strdup vs _strdup - size_t len = strlen(str)+1; - char *p = (char*) STBDS_REALLOC(NULL, 0, len); - memmove(p, str, len); - return p; -} - -#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MIN -#define STBDS_STRING_ARENA_BLOCKSIZE_MIN 512u -#endif -#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MAX -#define STBDS_STRING_ARENA_BLOCKSIZE_MAX (1u<<20) -#endif - -char *stbds_stralloc(stbds_string_arena *a, char *str) -{ - char *p; - size_t len = strlen(str)+1; - if (len > a->remaining) { - // compute the next blocksize - size_t blocksize = a->block; - - // size is 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, etc., so that - // there are log(SIZE) allocations to free when we destroy the table - blocksize = (size_t) (STBDS_STRING_ARENA_BLOCKSIZE_MIN) << (blocksize>>1); - - // if size is under 1M, advance to next blocktype - if (blocksize < (size_t)(STBDS_STRING_ARENA_BLOCKSIZE_MAX)) - ++a->block; - - if (len > blocksize) { - // if string is larger than blocksize, then just allocate the full size. - // note that we still advance string_block so block size will continue - // increasing, so e.g. if somebody only calls this with 1000-long strings, - // eventually the arena will start doubling and handling those as well - stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + len); - memmove(sb->storage, str, len); - if (a->storage) { - // insert it after the first element, so that we don't waste the space there - sb->next = a->storage->next; - a->storage->next = sb; - } else { - sb->next = 0; - a->storage = sb; - a->remaining = 0; // this is redundant, but good for clarity - } - return sb->storage; - } else { - stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + blocksize); - sb->next = a->storage; - a->storage = sb; - a->remaining = blocksize; - } - } - - STBDS_ASSERT(len <= a->remaining); - p = a->storage->storage + a->remaining - len; - a->remaining -= len; - memmove(p, str, len); - return p; -} - -void stbds_strreset(stbds_string_arena *a) -{ - stbds_string_block *x,*y; - x = a->storage; - while (x) { - y = x->next; - STBDS_FREE(NULL, x); - x = y; - } - memset(a, 0, sizeof(*a)); -} - -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// UNIT TESTS -// - -#ifdef STBDS_UNIT_TESTS -#include -#ifdef STBDS_ASSERT_WAS_UNDEFINED -#undef STBDS_ASSERT -#endif -#ifndef STBDS_ASSERT -#define STBDS_ASSERT assert -#include -#endif - -typedef struct { int key,b,c,d; } stbds_struct; -typedef struct { int key[2],b,c,d; } stbds_struct2; - -static char buffer[256]; -char *strkey(int n) -{ -#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) - sprintf_s(buffer, sizeof(buffer), "test_%d", n); -#else - sprintf(buffer, "test_%d", n); -#endif - return buffer; -} - -void stbds_unit_tests(void) -{ -#if defined(_MSC_VER) && _MSC_VER <= 1200 && defined(__cplusplus) - // VC6 C++ doesn't like the template<> trick on unnamed structures, so do nothing! - STBDS_ASSERT(0); -#else - const int testsize = 100000; - const int testsize2 = testsize/20; - int *arr=NULL; - struct { int key; int value; } *intmap = NULL; - struct { char *key; int value; } *strmap = NULL, s; - struct { stbds_struct key; int value; } *map = NULL; - stbds_struct *map2 = NULL; - stbds_struct2 *map3 = NULL; - stbds_string_arena sa = { 0 }; - int key3[2] = { 1,2 }; - ptrdiff_t temp; - - int i,j; - - STBDS_ASSERT(arrlen(arr)==0); - for (i=0; i < 20000; i += 50) { - for (j=0; j < i; ++j) - arrpush(arr,j); - arrfree(arr); - } - - for (i=0; i < 4; ++i) { - arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); - arrdel(arr,i); - arrfree(arr); - arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); - arrdelswap(arr,i); - arrfree(arr); - } - - for (i=0; i < 5; ++i) { - arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); - stbds_arrins(arr,i,5); - STBDS_ASSERT(arr[i] == 5); - if (i < 4) - STBDS_ASSERT(arr[4] == 4); - arrfree(arr); - } - - i = 1; - STBDS_ASSERT(hmgeti(intmap,i) == -1); - hmdefault(intmap, -2); - STBDS_ASSERT(hmgeti(intmap, i) == -1); - STBDS_ASSERT(hmget (intmap, i) == -2); - for (i=0; i < testsize; i+=2) - hmput(intmap, i, i*5); - for (i=0; i < testsize; i+=1) { - if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); - else STBDS_ASSERT(hmget(intmap, i) == i*5); - if (i & 1) STBDS_ASSERT(hmget_ts(intmap, i, temp) == -2 ); - else STBDS_ASSERT(hmget_ts(intmap, i, temp) == i*5); - } - for (i=0; i < testsize; i+=2) - hmput(intmap, i, i*3); - for (i=0; i < testsize; i+=1) - if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); - else STBDS_ASSERT(hmget(intmap, i) == i*3); - for (i=2; i < testsize; i+=4) - hmdel(intmap, i); // delete half the entries - for (i=0; i < testsize; i+=1) - if (i & 3) STBDS_ASSERT(hmget(intmap, i) == -2 ); - else STBDS_ASSERT(hmget(intmap, i) == i*3); - for (i=0; i < testsize; i+=1) - hmdel(intmap, i); // delete the rest of the entries - for (i=0; i < testsize; i+=1) - STBDS_ASSERT(hmget(intmap, i) == -2 ); - hmfree(intmap); - for (i=0; i < testsize; i+=2) - hmput(intmap, i, i*3); - hmfree(intmap); - - #if defined(__clang__) || defined(__GNUC__) - #ifndef __cplusplus - intmap = NULL; - hmput(intmap, 15, 7); - hmput(intmap, 11, 3); - hmput(intmap, 9, 5); - STBDS_ASSERT(hmget(intmap, 9) == 5); - STBDS_ASSERT(hmget(intmap, 11) == 3); - STBDS_ASSERT(hmget(intmap, 15) == 7); - #endif - #endif - - for (i=0; i < testsize; ++i) - stralloc(&sa, strkey(i)); - strreset(&sa); - - { - s.key = "a", s.value = 1; - shputs(strmap, s); - STBDS_ASSERT(*strmap[0].key == 'a'); - STBDS_ASSERT(strmap[0].key == s.key); - STBDS_ASSERT(strmap[0].value == s.value); - shfree(strmap); - } - - { - s.key = "a", s.value = 1; - sh_new_strdup(strmap); - shputs(strmap, s); - STBDS_ASSERT(*strmap[0].key == 'a'); - STBDS_ASSERT(strmap[0].key != s.key); - STBDS_ASSERT(strmap[0].value == s.value); - shfree(strmap); - } - - { - s.key = "a", s.value = 1; - sh_new_arena(strmap); - shputs(strmap, s); - STBDS_ASSERT(*strmap[0].key == 'a'); - STBDS_ASSERT(strmap[0].key != s.key); - STBDS_ASSERT(strmap[0].value == s.value); - shfree(strmap); - } - - for (j=0; j < 2; ++j) { - STBDS_ASSERT(shgeti(strmap,"foo") == -1); - if (j == 0) - sh_new_strdup(strmap); - else - sh_new_arena(strmap); - STBDS_ASSERT(shgeti(strmap,"foo") == -1); - shdefault(strmap, -2); - STBDS_ASSERT(shgeti(strmap,"foo") == -1); - for (i=0; i < testsize; i+=2) - shput(strmap, strkey(i), i*3); - for (i=0; i < testsize; i+=1) - if (i & 1) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); - else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); - for (i=2; i < testsize; i+=4) - shdel(strmap, strkey(i)); // delete half the entries - for (i=0; i < testsize; i+=1) - if (i & 3) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); - else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); - for (i=0; i < testsize; i+=1) - shdel(strmap, strkey(i)); // delete the rest of the entries - for (i=0; i < testsize; i+=1) - STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); - shfree(strmap); - } - - { - struct { char *key; char value; } *hash = NULL; - char name[4] = "jen"; - shput(hash, "bob" , 'h'); - shput(hash, "sally" , 'e'); - shput(hash, "fred" , 'l'); - shput(hash, "jen" , 'x'); - shput(hash, "doug" , 'o'); - - shput(hash, name , 'l'); - shfree(hash); - } - - for (i=0; i < testsize; i += 2) { - stbds_struct s = { i,i*2,i*3,i*4 }; - hmput(map, s, i*5); - } - - for (i=0; i < testsize; i += 1) { - stbds_struct s = { i,i*2,i*3 ,i*4 }; - stbds_struct t = { i,i*2,i*3+1,i*4 }; - if (i & 1) STBDS_ASSERT(hmget(map, s) == 0); - else STBDS_ASSERT(hmget(map, s) == i*5); - if (i & 1) STBDS_ASSERT(hmget_ts(map, s, temp) == 0); - else STBDS_ASSERT(hmget_ts(map, s, temp) == i*5); - //STBDS_ASSERT(hmget(map, t.key) == 0); - } - - for (i=0; i < testsize; i += 2) { - stbds_struct s = { i,i*2,i*3,i*4 }; - hmputs(map2, s); - } - hmfree(map); - - for (i=0; i < testsize; i += 1) { - stbds_struct s = { i,i*2,i*3,i*4 }; - stbds_struct t = { i,i*2,i*3+1,i*4 }; - if (i & 1) STBDS_ASSERT(hmgets(map2, s.key).d == 0); - else STBDS_ASSERT(hmgets(map2, s.key).d == i*4); - //STBDS_ASSERT(hmgetp(map2, t.key) == 0); - } - hmfree(map2); - - for (i=0; i < testsize; i += 2) { - stbds_struct2 s = { { i,i*2 }, i*3,i*4, i*5 }; - hmputs(map3, s); - } - for (i=0; i < testsize; i += 1) { - stbds_struct2 s = { { i,i*2}, i*3, i*4, i*5 }; - stbds_struct2 t = { { i,i*2}, i*3+1, i*4, i*5 }; - if (i & 1) STBDS_ASSERT(hmgets(map3, s.key).d == 0); - else STBDS_ASSERT(hmgets(map3, s.key).d == i*5); - //STBDS_ASSERT(hmgetp(map3, t.key) == 0); - } -#endif -} -#endif - - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2019 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ From dee8cc858b9631c7f6df6e2fa9d1a71119aa6edb Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Sun, 16 Nov 2025 19:58:49 +0900 Subject: [PATCH 19/29] check uid --- mdm/main.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mdm/main.c b/mdm/main.c index 7507601..f91ed1d 100644 --- a/mdm/main.c +++ b/mdm/main.c @@ -16,6 +16,11 @@ int main(int argc, char** argv){ } } + if(getuid() != 0){ + fprintf(stderr, "MDM has to be ran as root\n"); + return 1; + } + 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"); From 54efe25a241ed4e9196d637fbd63c95a09708c4d Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Mon, 17 Nov 2025 18:17:33 +0900 Subject: [PATCH 20/29] functional --- mdm/login.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++--- mdm/main.c | 39 ++++++++++++++++++++++++++----- mdm/mdm.h | 8 ++++++- mdm/xserver.c | 28 +++++++++++++++------- 4 files changed, 121 insertions(+), 18 deletions(-) diff --git a/mdm/login.c b/mdm/login.c index ae82750..0663f38 100644 --- a/mdm/login.c +++ b/mdm/login.c @@ -1,14 +1,52 @@ #include "mdm.h" +#include #include +#include MwWidget root; -MwWidget window, usercombo, passentry; +MwWidget window, sesscombo, usercombo, passentry; + +typedef struct session { + char* key; + char* value; +} session_t; + +session_t* sessions = NULL; +char* name_line; +char* exec_line; 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){ + MwComboBoxAdd(user, -1, value); + + name_line = MDEStringDuplicate(value); + }else if(strcmp(name, "Exec") == 0){ + exec_line = MDEStringDuplicate(value); + } + + return 1; +} + +static void add_session(const char* path, int dir, void* user){ + name_line = NULL; + exec_line = NULL; + + ini_parse(path, dumper, user); + + if(name_line != NULL && exec_line != NULL){ + shput(sessions, name_line, exec_line); + } + + if(name_line != NULL) free(name_line); +} + static int conversation(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata){ int i; @@ -47,6 +85,13 @@ static void try_login(MwWidget handle, void* user, void* client){ if(err){ MwSetText(passentry, MwNtext, ""); }else{ + struct passwd* pwd = getpwnam(username); + const char* session = MwComboBoxGet(sesscombo, MwGetInteger(sesscombo, MwNvalue)); + + uid = pwd->pw_uid; + gid = pwd->pw_gid; + run = shget(sessions, session); + MwDestroyWidget(root); } @@ -73,9 +118,12 @@ static void try_login(MwWidget handle, void* user, void* client){ } void login_window(void){ - MwWidget pic, userlabel, passlabel, mainsep, sesslabel, sesscombo, fieldsep, shutdown, reboot, ok; + MwWidget pic, userlabel, passlabel, mainsep, sesslabel, fieldsep, shutdown, reboot, ok; MwLLPixmap p; void* out; + int i; + + pthread_mutex_lock(&xmutex); MwLibraryInit(); @@ -123,7 +171,17 @@ void login_window(void){ MwAddUserHandler(ok, MwNactivateHandler, try_login, NULL); MwAddUserHandler(passentry, MwNactivateHandler, try_login, NULL); - MDEListUsers(add_user, usercombo); + 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, sesscombo); + + pthread_mutex_unlock(&xmutex); while(1){ int s = 0; diff --git a/mdm/main.c b/mdm/main.c index f91ed1d..2395551 100644 --- a/mdm/main.c +++ b/mdm/main.c @@ -2,8 +2,13 @@ int is_launch_x = 1; +gid_t gid; +uid_t uid; +char* run; + int main(int argc, char** argv){ int i, st; + char* backup = NULL; for(i = 1; i < argc; i++){ if(argv[i][0] == '-'){ @@ -26,15 +31,37 @@ int main(int argc, char** argv){ parse_config(); - if(is_launch_x){ - if((st = launch_x()) != 0) return st; - } + if(getenv("DISPLAY") != NULL) backup = MDEStringDuplicate(getenv("DISPLAY")); + do{ + pid_t pid; + + setenv("DISPLAY", backup == NULL ? "" : backup, 1); + if(is_launch_x && (st = launch_x()) != 0) return st; - while(1){ if((st = init_x()) != 0) return st; login_window(); - /* TODO: run session */ - } + if(run == NULL) continue; + + if((pid = fork()) == 0){ + struct passwd* pwd = getpwuid(uid); + + setgid(gid); + setuid(uid); + chdir(pwd->pw_dir); + setenv("USER", pwd->pw_name, 1); + setenv("SHELL", pwd->pw_shell, 1); + setenv("HOME", pwd->pw_dir, 1); + _exit(system(run)); + }else{ + waitpid(pid, NULL, 0); + } + + if(is_launch_x){ + kill_x(); + } + }while(is_launch_x); + + if(backup != NULL) free(backup); } diff --git a/mdm/mdm.h b/mdm/mdm.h index 60869bd..9e8be8d 100644 --- a/mdm/mdm.h +++ b/mdm/mdm.h @@ -22,6 +22,11 @@ #include #include +/* main.c */ +extern gid_t gid; +extern uid_t uid; +extern char* run; + /* xserver.c */ extern pthread_t xthread; extern pthread_mutex_t xmutex; @@ -31,7 +36,8 @@ int launch_x(void); int x_width(void); int x_height(void); int init_x(void); -void x_loop(void); +void loop_x(void); +void kill_x(void); /* login.c */ extern MwWidget root; diff --git a/mdm/xserver.c b/mdm/xserver.c index 21b6eed..9414346 100644 --- a/mdm/xserver.c +++ b/mdm/xserver.c @@ -4,7 +4,7 @@ #include "stb_ds.h" -static pid_t x_server; +static pid_t xserver; static int got_usr1; Display* xdisplay; @@ -22,13 +22,15 @@ int launch_x(void){ signal(SIGUSR1, catch_usr1); - if((x_server = fork()) == 0){ + /* TODO: Remove hardcoded DISPLAY */ + + if((xserver = fork()) == 0){ signal(SIGUSR1, SIG_IGN); execlp("X", "X", ":0", NULL); _exit(-1); }else{ while(!got_usr1){ - if(waitpid(x_server, NULL, WNOHANG) != 0){ + if(waitpid(xserver, NULL, WNOHANG) != 0){ fprintf(stderr, "server died. what happened?\n"); return 1; } @@ -50,7 +52,7 @@ static int wm_detect(Display* disp, XErrorEvent* ev){ } static void* x11_thread_routine(void* arg){ - x_loop(); + loop_x(); XCloseDisplay(xdisplay); } @@ -74,7 +76,10 @@ int init_x(void){ XSync(xdisplay, False); XSetErrorHandler(old); - if(wm_detected) return 1; + if(wm_detected){ + fprintf(stderr, "WM or something seem to be running\n"); + return 1; + } pthread_mutex_init(&xmutex, NULL); @@ -96,7 +101,7 @@ typedef struct window { Window client; } window_t; -void x_loop(void){ +void loop_x(void){ XEvent ev; window_t* frames = NULL; @@ -110,7 +115,7 @@ void x_loop(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, ev.xmaprequest.window, MwDefaultBorderWidth(root), MwDefaultBorderWidth(root)); + 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; @@ -135,18 +140,25 @@ void x_loop(void){ 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); - break; + if(c) break; } } } + +void kill_x(void){ + kill(xserver, SIGINT); + waitpid(xserver, NULL, 0); +} From bbbcfb97f5e4ec03b93fc07194a2349786fa5f10 Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Mon, 17 Nov 2025 18:20:36 +0900 Subject: [PATCH 21/29] working --- .clang-format | 21 ++++++++ format.sh | 3 ++ mdm/config.c | 4 +- mdm/login.c | 144 +++++++++++++++++++++++++------------------------- mdm/main.c | 26 ++++----- mdm/mdm.h | 12 ++--- mdm/xserver.c | 72 ++++++++++++------------- milkwm/main.c | 2 +- mpanel/main.c | 2 +- 9 files changed, 155 insertions(+), 131 deletions(-) create mode 100644 .clang-format create mode 100755 format.sh diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..f4cf343 --- /dev/null +++ b/.clang-format @@ -0,0 +1,21 @@ +--- +# $Id: .clang-format 567 2025-11-02 00:00:24Z nishi $ +Language: Cpp +UseTab: Always +TabWidth: 8 +AlignConsecutiveAssignments: + Enabled: true +AlignConsecutiveDeclarations: + Enabled: true +IndentWidth: 8 +PointerAlignment: Left +ColumnLimit: 0 +AllowShortIfStatementsOnASingleLine: Always +AllowShortBlocksOnASingleLine: Never +AllowShortFunctionsOnASingleLine: Empty +AllowShortLoopsOnASingleLine: true +SpaceBeforeParens: Never +AlignEscapedNewlines: DontAlign +SortIncludes: false +AllowShortEnumsOnASingleLine: false +#IndentPPDirectives: AfterHash diff --git a/format.sh b/format.sh new file mode 100755 index 0000000..2ea5006 --- /dev/null +++ b/format.sh @@ -0,0 +1,3 @@ +#!/bin/sh +DIRS="include milkwm mdm mpanel" +clang-format --verbose -i `find $DIRS -name "*.c" -or -name "*.h"` diff --git a/mdm/config.c b/mdm/config.c index 78fcbfa..bfb3f9e 100644 --- a/mdm/config.c +++ b/mdm/config.c @@ -4,7 +4,7 @@ char* config_picture = NULL; -static int dumper(void* user, const char* section, const char* name, const char* value){ +static int dumper(void* user, const char* section, const char* name, const char* value) { (void)user; if(strcmp(section, "MDM Config") != 0) return 1; @@ -14,6 +14,6 @@ static int dumper(void* user, const char* section, const char* name, const char* return 1; } -void parse_config(void){ +void parse_config(void) { ini_parse(CONFDIR "/mdm/mdmrc", dumper, NULL); } diff --git a/mdm/login.c b/mdm/login.c index 0663f38..606b110 100644 --- a/mdm/login.c +++ b/mdm/login.c @@ -13,53 +13,53 @@ typedef struct session { } session_t; session_t* sessions = NULL; -char* name_line; -char* exec_line; +char* name_line; +char* exec_line; -static void add_user(const char* name, void* user){ +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){ +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){ + if(strcmp(name, "Name") == 0) { MwComboBoxAdd(user, -1, value); name_line = MDEStringDuplicate(value); - }else if(strcmp(name, "Exec") == 0){ + } else if(strcmp(name, "Exec") == 0) { exec_line = MDEStringDuplicate(value); } return 1; } -static void add_session(const char* path, int dir, void* user){ +static void add_session(const char* path, int dir, void* user) { name_line = NULL; exec_line = NULL; ini_parse(path, dumper, user); - if(name_line != NULL && exec_line != NULL){ + if(name_line != NULL && exec_line != NULL) { shput(sessions, name_line, exec_line); } if(name_line != NULL) free(name_line); } -static int conversation(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata){ +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){ + 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{ + resp[i]->resp = MDEStringDuplicate(txt); + } else { return PAM_CONV_ERR; } } @@ -67,26 +67,26 @@ static int conversation(int num_msg, const struct pam_message** msg, struct pam_ return PAM_SUCCESS; } -static void try_login(MwWidget handle, void* user, void* client){ - const char* username = MwComboBoxGet(usercombo, MwGetInteger(usercombo, MwNvalue)); - struct pam_conv conv = {conversation, NULL}; - pam_handle_t* pam; - int ret; - jmp_buf cleanup; - int err = 1; +static void try_login(MwWidget handle, void* user, void* client) { + const char* username = MwComboBoxGet(usercombo, MwGetInteger(usercombo, MwNvalue)); + struct pam_conv conv = {conversation, NULL}; + pam_handle_t* pam; + int ret; + jmp_buf cleanup; + int err = 1; (void)handle; (void)user; (void)client; - if(setjmp(cleanup)){ + if(setjmp(cleanup)) { pam_end(pam, 0); - if(err){ + if(err) { MwSetText(passentry, MwNtext, ""); - }else{ - struct passwd* pwd = getpwnam(username); - const char* session = MwComboBoxGet(sesscombo, MwGetInteger(sesscombo, MwNvalue)); + } else { + struct passwd* pwd = getpwnam(username); + const char* session = MwComboBoxGet(sesscombo, MwGetInteger(sesscombo, MwNvalue)); uid = pwd->pw_uid; gid = pwd->pw_gid; @@ -99,17 +99,17 @@ static void try_login(MwWidget handle, void* user, void* client){ } ret = pam_start("mdm", username, &conv, &pam); - if(ret != PAM_SUCCESS){ + if(ret != PAM_SUCCESS) { longjmp(cleanup, 1); } ret = pam_authenticate(pam, 0); - if(ret != PAM_SUCCESS){ + if(ret != PAM_SUCCESS) { longjmp(cleanup, 1); } ret = pam_acct_mgmt(pam, 0); - if(ret != PAM_SUCCESS){ + if(ret != PAM_SUCCESS) { longjmp(cleanup, 1); } @@ -117,54 +117,54 @@ static void try_login(MwWidget handle, void* user, void* client){ longjmp(cleanup, 1); } -void login_window(void){ - MwWidget pic, userlabel, passlabel, mainsep, sesslabel, fieldsep, shutdown, reboot, ok; +void login_window(void) { + MwWidget pic, userlabel, passlabel, mainsep, sesslabel, fieldsep, shutdown, reboot, ok; MwLLPixmap p; - void* out; - int i; + 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() - 183) / 2, 366, 183, - MwNtitle, "login", - NULL); - p = config_picture == NULL ? NULL : MwLoadImage(window, config_picture); - pic = MwVaCreateWidget(MwImageClass, "image", window, 10, 10, 80, 137, - MwNhasBorder, 1, - NULL); + + window = MwVaCreateWidget(MwWindowClass, "login", root, (x_width() - 366) / 2, (x_height() - 183) / 2, 366, 183, + 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); + 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); if(p != NULL) MwVaApply(pic, MwNpixmap, p, NULL); @@ -173,21 +173,21 @@ void login_window(void){ MDEUsersList(add_user, usercombo); - for(i = 0; i < shlen(sessions); i++){ + 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, sesscombo); - + pthread_mutex_unlock(&xmutex); - while(1){ + while(1) { int s = 0; pthread_mutex_lock(&xmutex); - while(MwPending(root)){ + while(MwPending(root)) { if((s = MwStep(root)) != 0) break; } pthread_mutex_unlock(&xmutex); diff --git a/mdm/main.c b/mdm/main.c index 2395551..05c6360 100644 --- a/mdm/main.c +++ b/mdm/main.c @@ -6,22 +6,22 @@ gid_t gid; uid_t uid; char* run; -int main(int argc, char** argv){ - int i, st; +int main(int argc, char** argv) { + int i, st; char* backup = NULL; - - for(i = 1; i < argc; i++){ - if(argv[i][0] == '-'){ - if(strcmp(argv[i], "-X") == 0 || strcmp(argv[i], "--no-launch-x") == 0){ + + 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{ + } else { fprintf(stderr, "%s: bad option: %s\n", argv[0], argv[i]); return 1; } } } - if(getuid() != 0){ + if(getuid() != 0) { fprintf(stderr, "MDM has to be ran as root\n"); return 1; } @@ -32,7 +32,7 @@ int main(int argc, char** argv){ parse_config(); if(getenv("DISPLAY") != NULL) backup = MDEStringDuplicate(getenv("DISPLAY")); - do{ + do { pid_t pid; setenv("DISPLAY", backup == NULL ? "" : backup, 1); @@ -44,7 +44,7 @@ int main(int argc, char** argv){ if(run == NULL) continue; - if((pid = fork()) == 0){ + if((pid = fork()) == 0) { struct passwd* pwd = getpwuid(uid); setgid(gid); @@ -54,14 +54,14 @@ int main(int argc, char** argv){ setenv("SHELL", pwd->pw_shell, 1); setenv("HOME", pwd->pw_dir, 1); _exit(system(run)); - }else{ + } else { waitpid(pid, NULL, 0); } - if(is_launch_x){ + if(is_launch_x) { kill_x(); } - }while(is_launch_x); + } while(is_launch_x); if(backup != NULL) free(backup); } diff --git a/mdm/mdm.h b/mdm/mdm.h index 9e8be8d..fa93b51 100644 --- a/mdm/mdm.h +++ b/mdm/mdm.h @@ -28,14 +28,14 @@ extern uid_t uid; extern char* run; /* xserver.c */ -extern pthread_t xthread; +extern pthread_t xthread; extern pthread_mutex_t xmutex; -extern Display* xdisplay; +extern Display* xdisplay; -int launch_x(void); -int x_width(void); -int x_height(void); -int init_x(void); +int launch_x(void); +int x_width(void); +int x_height(void); +int init_x(void); void loop_x(void); void kill_x(void); diff --git a/mdm/xserver.c b/mdm/xserver.c index 9414346..e1a42c1 100644 --- a/mdm/xserver.c +++ b/mdm/xserver.c @@ -5,32 +5,32 @@ #include "stb_ds.h" static pid_t xserver; -static int got_usr1; +static int got_usr1; -Display* xdisplay; -pthread_t xthread; +Display* xdisplay; +pthread_t xthread; pthread_mutex_t xmutex; -static void catch_usr1(int sig){ +static void catch_usr1(int sig) { (void)sig; got_usr1 = 1; } -int launch_x(void){ +int launch_x(void) { got_usr1 = 0; signal(SIGUSR1, catch_usr1); /* TODO: Remove hardcoded DISPLAY */ - if((xserver = fork()) == 0){ + 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){ + } else { + while(!got_usr1) { + if(waitpid(xserver, NULL, WNOHANG) != 0) { fprintf(stderr, "server died. what happened?\n"); return 1; } @@ -44,27 +44,27 @@ int launch_x(void){ } static int wm_detected = 0; -static int wm_detect(Display* disp, XErrorEvent* ev){ +static int wm_detect(Display* disp, XErrorEvent* ev) { (void)disp; (void)ev; wm_detected = 1; } -static void* x11_thread_routine(void* arg){ +static void* x11_thread_routine(void* arg) { loop_x(); XCloseDisplay(xdisplay); } -int init_x(void){ +int init_x(void) { XColor bg; - void* old; + void* old; xdisplay = XOpenDisplay(NULL); - bg.red = 0x1c * 256; + bg.red = 0x1c * 256; bg.green = 0x45 * 256; - bg.blue = 0x73 * 256; + bg.blue = 0x73 * 256; XAllocColor(xdisplay, DefaultColormap(xdisplay, DefaultScreen(xdisplay)), &bg); XSetWindowBackground(xdisplay, DefaultRootWindow(xdisplay), bg.pixel); @@ -76,7 +76,7 @@ int init_x(void){ XSync(xdisplay, False); XSetErrorHandler(old); - if(wm_detected){ + if(wm_detected) { fprintf(stderr, "WM or something seem to be running\n"); return 1; } @@ -88,40 +88,40 @@ int init_x(void){ return 0; } -int x_width(void){ +int x_width(void) { return DisplayWidth(xdisplay, DefaultScreen(xdisplay)); } -int x_height(void){ +int x_height(void) { return DisplayHeight(xdisplay, DefaultScreen(xdisplay)); } typedef struct window { MwWidget frame; - Window client; + Window client; } window_t; -void loop_x(void){ - XEvent ev; +void loop_x(void) { + XEvent ev; window_t* frames = NULL; - while(1){ + while(1) { XNextEvent(xdisplay, &ev); - if(ev.type == MapRequest){ + if(ev.type == MapRequest) { XWindowAttributes xwa; - window_t w; - int i; + 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){ + 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)){ + if(i != arrlen(frames)) { pthread_mutex_unlock(&xmutex); continue; } @@ -129,22 +129,22 @@ void loop_x(void){ 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); + 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){ + } 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){ + for(i = 0; i < arrlen(frames); i++) { + if(frames[i].client == ev.xunmap.window) { MwDestroyWidget(frames[i].frame); arrdel(frames, i); c = 1; @@ -158,7 +158,7 @@ void loop_x(void){ } } -void kill_x(void){ +void kill_x(void) { kill(xserver, SIGINT); waitpid(xserver, NULL, 0); } diff --git a/milkwm/main.c b/milkwm/main.c index 979a1cd..237c8ce 100644 --- a/milkwm/main.c +++ b/milkwm/main.c @@ -1 +1 @@ -int main(){} +int main() {} diff --git a/mpanel/main.c b/mpanel/main.c index 979a1cd..237c8ce 100644 --- a/mpanel/main.c +++ b/mpanel/main.c @@ -1 +1 @@ -int main(){} +int main() {} From 8ffa6a6ed12752170550116fefec251f0538b1c3 Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Mon, 17 Nov 2025 18:25:04 +0900 Subject: [PATCH 22/29] nvm --- .clang-format | 21 --------------------- format.sh | 3 --- 2 files changed, 24 deletions(-) delete mode 100644 .clang-format delete mode 100755 format.sh diff --git a/.clang-format b/.clang-format deleted file mode 100644 index f4cf343..0000000 --- a/.clang-format +++ /dev/null @@ -1,21 +0,0 @@ ---- -# $Id: .clang-format 567 2025-11-02 00:00:24Z nishi $ -Language: Cpp -UseTab: Always -TabWidth: 8 -AlignConsecutiveAssignments: - Enabled: true -AlignConsecutiveDeclarations: - Enabled: true -IndentWidth: 8 -PointerAlignment: Left -ColumnLimit: 0 -AllowShortIfStatementsOnASingleLine: Always -AllowShortBlocksOnASingleLine: Never -AllowShortFunctionsOnASingleLine: Empty -AllowShortLoopsOnASingleLine: true -SpaceBeforeParens: Never -AlignEscapedNewlines: DontAlign -SortIncludes: false -AllowShortEnumsOnASingleLine: false -#IndentPPDirectives: AfterHash diff --git a/format.sh b/format.sh deleted file mode 100755 index 2ea5006..0000000 --- a/format.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -DIRS="include milkwm mdm mpanel" -clang-format --verbose -i `find $DIRS -name "*.c" -or -name "*.h"` From 4f590f931815b731ebafddcf2b4d583dd87f7370 Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Tue, 18 Nov 2025 16:12:16 +0900 Subject: [PATCH 23/29] daemonize --- mdm/main.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/mdm/main.c b/mdm/main.c index 05c6360..97a9738 100644 --- a/mdm/main.c +++ b/mdm/main.c @@ -1,6 +1,7 @@ #include "mdm.h" int is_launch_x = 1; +int is_daemon = 1; gid_t gid; uid_t uid; @@ -9,11 +10,14 @@ char* run; int main(int argc, char** argv) { int i, st; 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; @@ -26,6 +30,13 @@ int main(int argc, char** argv) { 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 "/mdm", F_OK) != 0) mkdir(CONFDIR "/mdm", 0755); if(access(CONFDIR "/mdm/mdmrc", F_OK) != 0) MDEFileCopy(DATADIR "/examples/mdm/mdmrc", CONFDIR "/mdm/mdmrc"); @@ -33,8 +44,6 @@ int main(int argc, char** argv) { if(getenv("DISPLAY") != NULL) backup = MDEStringDuplicate(getenv("DISPLAY")); do { - pid_t pid; - setenv("DISPLAY", backup == NULL ? "" : backup, 1); if(is_launch_x && (st = launch_x()) != 0) return st; From 87502cc81fb3eb52330bf00d6f07d8c158a3e5af Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Tue, 18 Nov 2025 17:17:04 +0900 Subject: [PATCH 24/29] fix --- mdm/login.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mdm/login.c b/mdm/login.c index 606b110..2bb509d 100644 --- a/mdm/login.c +++ b/mdm/login.c @@ -180,6 +180,7 @@ void login_window(void) { sh_new_strdup(sessions); shdefault(sessions, NULL); MDEDirectoryScan(DATADIR "/xsessions", add_session, sesscombo); + MDEDirectoryScan("/usr/share/xsessions", add_session, sesscombo); pthread_mutex_unlock(&xmutex); From 290aed5dcda6980ed51d963e6b3a7a4661fe703f Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Tue, 18 Nov 2025 17:51:14 +0900 Subject: [PATCH 25/29] fix --- mdm/login.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/mdm/login.c b/mdm/login.c index 2bb509d..47befa7 100644 --- a/mdm/login.c +++ b/mdm/login.c @@ -24,8 +24,6 @@ static int dumper(void* user, const char* section, const char* name, const char* if(strcmp(section, "Desktop Entry") != 0) return 1; if(strcmp(name, "Name") == 0) { - MwComboBoxAdd(user, -1, value); - name_line = MDEStringDuplicate(value); } else if(strcmp(name, "Exec") == 0) { exec_line = MDEStringDuplicate(value); @@ -34,17 +32,33 @@ static int dumper(void* user, const char* section, const char* name, const char* return 1; } -static void add_session(const char* path, int dir, void* user) { +static void add_session(const char* path, int dir, int symlink, void* user) { + int m = 1; + name_line = NULL; exec_line = NULL; + + if(dir || symlink) return; ini_parse(path, dumper, user); if(name_line != NULL && exec_line != NULL) { - shput(sessions, name_line, exec_line); + 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); + + shput(sessions, name_line, exec_line); + + m = 0; + } } if(name_line != NULL) free(name_line); + if(m && exec_line != NULL) free(exec_line); } static int conversation(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata) { From 8121d8296fd179a509c08c1b9f75e6742ae508d4 Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Tue, 18 Nov 2025 17:56:40 +0900 Subject: [PATCH 26/29] update --- mdm/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mdm/main.c b/mdm/main.c index 97a9738..0f5c5f8 100644 --- a/mdm/main.c +++ b/mdm/main.c @@ -37,6 +37,7 @@ int main(int argc, char** argv) { 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"); From 879f365905fc2f1fc277e9d82710b585cdd71c17 Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Tue, 18 Nov 2025 19:16:40 +0900 Subject: [PATCH 27/29] update --- CMakeLists.txt | 23 ----------------------- include/config.h.in | 8 -------- mdm/CMakeLists.txt | 28 ++++------------------------ milkwm/CMakeLists.txt | 27 +-------------------------- mpanel/CMakeLists.txt | 26 +------------------------- 5 files changed, 6 insertions(+), 106 deletions(-) delete mode 100644 include/config.h.in diff --git a/CMakeLists.txt b/CMakeLists.txt index f0d4fd0..e7f87e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,29 +3,6 @@ project( mdebase ) -macro(PKGCONF_ADD dest name) - pkg_check_modules(${name} REQUIRED ${name}) - target_include_directories( - ${dest} - PRIVATE - ${${name}_INCLUDE_DIRS} - ) - target_link_directories( - ${dest} - PRIVATE - ${${name}_LIBRARY_DIRS} - ) - target_link_libraries( - ${dest} - PRIVATE - ${${name}_LIBRARIES} - ) -endmacro() - -include(GNUInstallDirs) -configure_file(include/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - add_subdirectory(mdm) add_subdirectory(milkwm) add_subdirectory(mpanel) diff --git a/include/config.h.in b/include/config.h.in deleted file mode 100644 index f041cba..0000000 --- a/include/config.h.in +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef __CONFIG_H__ -#define __CONFIG_H__ - -#define PREFIX "@CMAKE_INSTALL_PREFIX@" -#define CONFDIR "@CMAKE_INSTALL_FULL_SYSCONFDIR@" -#define DATADIR "@CMAKE_INSTALL_FULL_DATAROOTDIR@" - -#endif diff --git a/mdm/CMakeLists.txt b/mdm/CMakeLists.txt index 88b61d9..160c341 100644 --- a/mdm/CMakeLists.txt +++ b/mdm/CMakeLists.txt @@ -1,37 +1,17 @@ -project( - mdm -) +setup_project(mdm ${CMAKE_INSTALL_SBINDIR}) -find_package(PkgConfig) - -file( - GLOB - mdm_SRC - *.c -) - -add_executable( - mdm - ${mdm_SRC} -) - -PKGCONF_ADD(mdm x11) -PKGCONF_ADD(mdm inih) +pkg_config_add(mdm x11) +pkg_config_add(mdm inih) target_link_libraries( mdm PRIVATE - pam pthread MDELib Mw + pam pthread ) - include(GNUInstallDirs) configure_file(mdmrc.in mdmrc) -install( - TARGETS mdm - RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} -) install( FILES pam RENAME mdm diff --git a/milkwm/CMakeLists.txt b/milkwm/CMakeLists.txt index 9c964ed..444833d 100644 --- a/milkwm/CMakeLists.txt +++ b/milkwm/CMakeLists.txt @@ -1,26 +1 @@ -project( - milkwm -) - -file( - GLOB - milkwm_SRC - *.c -) - -add_executable( - milkwm - ${milkwm_SRC} -) - -target_link_libraries( - milkwm - PRIVATE - MDELib -) - -include(GNUInstallDirs) -install( - TARGETS milkwm - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -) +setup_project(milkwm ${CMAKE_INSTALL_BINDIR}) diff --git a/mpanel/CMakeLists.txt b/mpanel/CMakeLists.txt index 6fe1d22..4291ba0 100644 --- a/mpanel/CMakeLists.txt +++ b/mpanel/CMakeLists.txt @@ -1,23 +1,5 @@ -project( - mpanel -) +setup_project(mpanel ${CMAKE_INSTALL_BINDIR}) -file( - GLOB - mpanel_SRC - *.c -) - -add_executable( - mpanel - ${mpanel_SRC} -) - -target_link_libraries( - mpanel - PRIVATE - MDELib -) if(${CMAKE_SYSTEM_NAME} STREQUAL Linux) target_link_libraries( mpanel @@ -25,9 +7,3 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL Linux) dl ) endif() - -include(GNUInstallDirs) -install( - TARGETS mpanel - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -) From 1f5eb4255d07d0a076c7ea1673fb898eb984ea1d Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Tue, 18 Nov 2025 19:19:41 +0900 Subject: [PATCH 28/29] fix --- mdm/CMakeLists.txt | 2 ++ milkwm/CMakeLists.txt | 1 + mpanel/CMakeLists.txt | 1 + 3 files changed, 4 insertions(+) diff --git a/mdm/CMakeLists.txt b/mdm/CMakeLists.txt index 160c341..35acd2c 100644 --- a/mdm/CMakeLists.txt +++ b/mdm/CMakeLists.txt @@ -1,3 +1,5 @@ +include(MDEBase) +include(MDEPkgConfig) setup_project(mdm ${CMAKE_INSTALL_SBINDIR}) pkg_config_add(mdm x11) diff --git a/milkwm/CMakeLists.txt b/milkwm/CMakeLists.txt index 444833d..1c6db46 100644 --- a/milkwm/CMakeLists.txt +++ b/milkwm/CMakeLists.txt @@ -1 +1,2 @@ +include(MDEBase) setup_project(milkwm ${CMAKE_INSTALL_BINDIR}) diff --git a/mpanel/CMakeLists.txt b/mpanel/CMakeLists.txt index 4291ba0..8e1c41b 100644 --- a/mpanel/CMakeLists.txt +++ b/mpanel/CMakeLists.txt @@ -1,3 +1,4 @@ +include(MDEBase) setup_project(mpanel ${CMAKE_INSTALL_BINDIR}) if(${CMAKE_SYSTEM_NAME} STREQUAL Linux) From 6ff2d27e31b995b71fd66407e3c5d3f74ce9ea75 Mon Sep 17 00:00:00 2001 From: NishiOwO Date: Tue, 18 Nov 2025 22:51:17 +0900 Subject: [PATCH 29/29] do not install --- mdm/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mdm/CMakeLists.txt b/mdm/CMakeLists.txt index 35acd2c..7ff4ebe 100644 --- a/mdm/CMakeLists.txt +++ b/mdm/CMakeLists.txt @@ -17,7 +17,7 @@ configure_file(mdmrc.in mdmrc) install( FILES pam RENAME mdm - DESTINATION /etc/pam.d + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/examples/pam.d ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/mdmrc