This commit is contained in:
Nishi 2025-11-17 18:20:36 +09:00
commit bbbcfb97f5
Signed by: nishi
GPG key ID: 27EF69B208EB9343
9 changed files with 155 additions and 131 deletions

21
.clang-format Normal file
View file

@ -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

3
format.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/sh
DIRS="include milkwm mdm mpanel"
clang-format --verbose -i `find $DIRS -name "*.c" -or -name "*.h"`

View file

@ -4,7 +4,7 @@
char* config_picture = NULL; 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; (void)user;
if(strcmp(section, "MDM Config") != 0) return 1; 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; return 1;
} }
void parse_config(void){ void parse_config(void) {
ini_parse(CONFDIR "/mdm/mdmrc", dumper, NULL); ini_parse(CONFDIR "/mdm/mdmrc", dumper, NULL);
} }

View file

@ -13,53 +13,53 @@ typedef struct session {
} session_t; } session_t;
session_t* sessions = NULL; session_t* sessions = NULL;
char* name_line; char* name_line;
char* exec_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); 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(section, "Desktop Entry") != 0) return 1;
if(strcmp(name, "Name") == 0){ if(strcmp(name, "Name") == 0) {
MwComboBoxAdd(user, -1, value); MwComboBoxAdd(user, -1, value);
name_line = MDEStringDuplicate(value); name_line = MDEStringDuplicate(value);
}else if(strcmp(name, "Exec") == 0){ } else if(strcmp(name, "Exec") == 0) {
exec_line = MDEStringDuplicate(value); exec_line = MDEStringDuplicate(value);
} }
return 1; 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; name_line = NULL;
exec_line = NULL; exec_line = NULL;
ini_parse(path, dumper, user); 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); shput(sessions, name_line, exec_line);
} }
if(name_line != NULL) free(name_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; int i;
*resp = malloc(num_msg * sizeof(struct pam_response)); *resp = malloc(num_msg * sizeof(struct pam_response));
for(i = 0; i < num_msg; i++){ for(i = 0; i < num_msg; i++) {
if(msg[i]->msg_style == PAM_PROMPT_ECHO_OFF){ if(msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) {
const char* txt = MwGetText(passentry, MwNtext); const char* txt = MwGetText(passentry, MwNtext);
if(txt == NULL) txt = ""; if(txt == NULL) txt = "";
resp[i]->resp_retcode = PAM_SUCCESS; resp[i]->resp_retcode = PAM_SUCCESS;
resp[i]->resp = MDEStringDuplicate(txt); resp[i]->resp = MDEStringDuplicate(txt);
}else{ } else {
return PAM_CONV_ERR; return PAM_CONV_ERR;
} }
} }
@ -67,26 +67,26 @@ static int conversation(int num_msg, const struct pam_message** msg, struct pam_
return PAM_SUCCESS; return PAM_SUCCESS;
} }
static void try_login(MwWidget handle, void* user, void* client){ static void try_login(MwWidget handle, void* user, void* client) {
const char* username = MwComboBoxGet(usercombo, MwGetInteger(usercombo, MwNvalue)); const char* username = MwComboBoxGet(usercombo, MwGetInteger(usercombo, MwNvalue));
struct pam_conv conv = {conversation, NULL}; struct pam_conv conv = {conversation, NULL};
pam_handle_t* pam; pam_handle_t* pam;
int ret; int ret;
jmp_buf cleanup; jmp_buf cleanup;
int err = 1; int err = 1;
(void)handle; (void)handle;
(void)user; (void)user;
(void)client; (void)client;
if(setjmp(cleanup)){ if(setjmp(cleanup)) {
pam_end(pam, 0); pam_end(pam, 0);
if(err){ if(err) {
MwSetText(passentry, MwNtext, ""); MwSetText(passentry, MwNtext, "");
}else{ } else {
struct passwd* pwd = getpwnam(username); struct passwd* pwd = getpwnam(username);
const char* session = MwComboBoxGet(sesscombo, MwGetInteger(sesscombo, MwNvalue)); const char* session = MwComboBoxGet(sesscombo, MwGetInteger(sesscombo, MwNvalue));
uid = pwd->pw_uid; uid = pwd->pw_uid;
gid = pwd->pw_gid; 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); ret = pam_start("mdm", username, &conv, &pam);
if(ret != PAM_SUCCESS){ if(ret != PAM_SUCCESS) {
longjmp(cleanup, 1); longjmp(cleanup, 1);
} }
ret = pam_authenticate(pam, 0); ret = pam_authenticate(pam, 0);
if(ret != PAM_SUCCESS){ if(ret != PAM_SUCCESS) {
longjmp(cleanup, 1); longjmp(cleanup, 1);
} }
ret = pam_acct_mgmt(pam, 0); ret = pam_acct_mgmt(pam, 0);
if(ret != PAM_SUCCESS){ if(ret != PAM_SUCCESS) {
longjmp(cleanup, 1); longjmp(cleanup, 1);
} }
@ -117,54 +117,54 @@ static void try_login(MwWidget handle, void* user, void* client){
longjmp(cleanup, 1); longjmp(cleanup, 1);
} }
void login_window(void){ void login_window(void) {
MwWidget pic, userlabel, passlabel, mainsep, sesslabel, fieldsep, shutdown, reboot, ok; MwWidget pic, userlabel, passlabel, mainsep, sesslabel, fieldsep, shutdown, reboot, ok;
MwLLPixmap p; MwLLPixmap p;
void* out; void* out;
int i; int i;
pthread_mutex_lock(&xmutex); pthread_mutex_lock(&xmutex);
MwLibraryInit(); MwLibraryInit();
root = MwCreateWidget(NULL, "root", NULL, 0, 0, 0, 0); root = MwCreateWidget(NULL, "root", NULL, 0, 0, 0, 0);
window = MwVaCreateWidget(MwWindowClass, "login", root, (x_width() - 366) / 2, (x_height() - 183) / 2, 366, 183, window = MwVaCreateWidget(MwWindowClass, "login", root, (x_width() - 366) / 2, (x_height() - 183) / 2, 366, 183,
MwNtitle, "login", MwNtitle, "login",
NULL); NULL);
p = config_picture == NULL ? NULL : MwLoadImage(window, config_picture); p = config_picture == NULL ? NULL : MwLoadImage(window, config_picture);
pic = MwVaCreateWidget(MwImageClass, "image", window, 10, 10, 80, 137, pic = MwVaCreateWidget(MwImageClass, "image", window, 10, 10, 80, 137,
MwNhasBorder, 1, MwNhasBorder, 1,
NULL); NULL);
userlabel = MwVaCreateWidget(MwLabelClass, "userlabel", window, 95, 10, MwTextWidth(window, "User:"), 16, userlabel = MwVaCreateWidget(MwLabelClass, "userlabel", window, 95, 10, MwTextWidth(window, "User:"), 16,
MwNtext, "User:", MwNtext, "User:",
NULL); NULL);
usercombo = MwCreateWidget(MwComboBoxClass, "usercombo", window, 95, 10+16+5, 265, 18); 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, passlabel = MwVaCreateWidget(MwLabelClass, "passlabel", window, 95, 10 + 16 + 5 + 18 + 5, MwTextWidth(window, "Password:"), 16,
MwNtext, "Password:", MwNtext, "Password:",
NULL); NULL);
passentry = MwVaCreateWidget(MwEntryClass, "passentry", window, 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, MwNhideInput, 1,
NULL); NULL);
mainsep = MwVaCreateWidget(MwSeparatorClass, "mainsep", window, 95, 10+16+5+18+5+16+5+18, 265, 10, mainsep = MwVaCreateWidget(MwSeparatorClass, "mainsep", window, 95, 10 + 16 + 5 + 18 + 5 + 16 + 5 + 18, 265, 10,
MwNorientation, MwHORIZONTAL, MwNorientation, MwHORIZONTAL,
NULL); NULL);
sesslabel = MwVaCreateWidget(MwLabelClass, "sesslabel", window, 95, 10+16+5+18+5+16+5+18+10, MwTextWidth(window, "Session Type:"), 16, sesslabel = MwVaCreateWidget(MwLabelClass, "sesslabel", window, 95, 10 + 16 + 5 + 18 + 5 + 16 + 5 + 18 + 10, MwTextWidth(window, "Session Type:"), 16,
MwNtext, "Session Type:", MwNtext, "Session Type:",
NULL); NULL);
sesscombo = MwCreateWidget(MwComboBoxClass, "sesscombo", window, 95, 10+16+5+18+5+16+5+18+10+16+5, 265, 18); 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, fieldsep = MwVaCreateWidget(MwSeparatorClass, "fieldsep", window, 10, 10 + 137, 351, 10,
MwNorientation, MwHORIZONTAL, MwNorientation, MwHORIZONTAL,
NULL); NULL);
shutdown = MwVaCreateWidget(MwButtonClass, "shutdown", window, 10, 10+137+10, 80, 18, shutdown = MwVaCreateWidget(MwButtonClass, "shutdown", window, 10, 10 + 137 + 10, 80, 18,
MwNtext, "Shut Down", MwNtext, "Shut Down",
NULL); NULL);
reboot = MwVaCreateWidget(MwButtonClass, "reboot", window, 10+80+5, 10+137+10, 70, 18, reboot = MwVaCreateWidget(MwButtonClass, "reboot", window, 10 + 80 + 5, 10 + 137 + 10, 70, 18,
MwNtext, "Reboot", MwNtext, "Reboot",
NULL); NULL);
ok = MwVaCreateWidget(MwButtonClass, "ok", window, 366 - 10 - 60, 10+137+10, 60, 18, ok = MwVaCreateWidget(MwButtonClass, "ok", window, 366 - 10 - 60, 10 + 137 + 10, 60, 18,
MwNtext, "OK", MwNtext, "OK",
NULL); NULL);
if(p != NULL) MwVaApply(pic, MwNpixmap, p, NULL); if(p != NULL) MwVaApply(pic, MwNpixmap, p, NULL);
@ -173,21 +173,21 @@ void login_window(void){
MDEUsersList(add_user, usercombo); MDEUsersList(add_user, usercombo);
for(i = 0; i < shlen(sessions); i++){ for(i = 0; i < shlen(sessions); i++) {
free(sessions[i].value); free(sessions[i].value);
} }
shfree(sessions); shfree(sessions);
sh_new_strdup(sessions); sh_new_strdup(sessions);
shdefault(sessions, NULL); shdefault(sessions, NULL);
MDEDirectoryScan(DATADIR "/xsessions", add_session, sesscombo); MDEDirectoryScan(DATADIR "/xsessions", add_session, sesscombo);
pthread_mutex_unlock(&xmutex); pthread_mutex_unlock(&xmutex);
while(1){ while(1) {
int s = 0; int s = 0;
pthread_mutex_lock(&xmutex); pthread_mutex_lock(&xmutex);
while(MwPending(root)){ while(MwPending(root)) {
if((s = MwStep(root)) != 0) break; if((s = MwStep(root)) != 0) break;
} }
pthread_mutex_unlock(&xmutex); pthread_mutex_unlock(&xmutex);

View file

@ -6,22 +6,22 @@ gid_t gid;
uid_t uid; uid_t uid;
char* run; char* run;
int main(int argc, char** argv){ int main(int argc, char** argv) {
int i, st; int i, st;
char* backup = NULL; char* backup = NULL;
for(i = 1; i < argc; i++){ for(i = 1; i < argc; i++) {
if(argv[i][0] == '-'){ if(argv[i][0] == '-') {
if(strcmp(argv[i], "-X") == 0 || strcmp(argv[i], "--no-launch-x") == 0){ if(strcmp(argv[i], "-X") == 0 || strcmp(argv[i], "--no-launch-x") == 0) {
is_launch_x = 0; is_launch_x = 0;
}else{ } else {
fprintf(stderr, "%s: bad option: %s\n", argv[0], argv[i]); fprintf(stderr, "%s: bad option: %s\n", argv[0], argv[i]);
return 1; return 1;
} }
} }
} }
if(getuid() != 0){ if(getuid() != 0) {
fprintf(stderr, "MDM has to be ran as root\n"); fprintf(stderr, "MDM has to be ran as root\n");
return 1; return 1;
} }
@ -32,7 +32,7 @@ int main(int argc, char** argv){
parse_config(); parse_config();
if(getenv("DISPLAY") != NULL) backup = MDEStringDuplicate(getenv("DISPLAY")); if(getenv("DISPLAY") != NULL) backup = MDEStringDuplicate(getenv("DISPLAY"));
do{ do {
pid_t pid; pid_t pid;
setenv("DISPLAY", backup == NULL ? "" : backup, 1); setenv("DISPLAY", backup == NULL ? "" : backup, 1);
@ -44,7 +44,7 @@ int main(int argc, char** argv){
if(run == NULL) continue; if(run == NULL) continue;
if((pid = fork()) == 0){ if((pid = fork()) == 0) {
struct passwd* pwd = getpwuid(uid); struct passwd* pwd = getpwuid(uid);
setgid(gid); setgid(gid);
@ -54,14 +54,14 @@ int main(int argc, char** argv){
setenv("SHELL", pwd->pw_shell, 1); setenv("SHELL", pwd->pw_shell, 1);
setenv("HOME", pwd->pw_dir, 1); setenv("HOME", pwd->pw_dir, 1);
_exit(system(run)); _exit(system(run));
}else{ } else {
waitpid(pid, NULL, 0); waitpid(pid, NULL, 0);
} }
if(is_launch_x){ if(is_launch_x) {
kill_x(); kill_x();
} }
}while(is_launch_x); } while(is_launch_x);
if(backup != NULL) free(backup); if(backup != NULL) free(backup);
} }

View file

@ -28,14 +28,14 @@ extern uid_t uid;
extern char* run; extern char* run;
/* xserver.c */ /* xserver.c */
extern pthread_t xthread; extern pthread_t xthread;
extern pthread_mutex_t xmutex; extern pthread_mutex_t xmutex;
extern Display* xdisplay; extern Display* xdisplay;
int launch_x(void); int launch_x(void);
int x_width(void); int x_width(void);
int x_height(void); int x_height(void);
int init_x(void); int init_x(void);
void loop_x(void); void loop_x(void);
void kill_x(void); void kill_x(void);

View file

@ -5,32 +5,32 @@
#include "stb_ds.h" #include "stb_ds.h"
static pid_t xserver; static pid_t xserver;
static int got_usr1; static int got_usr1;
Display* xdisplay; Display* xdisplay;
pthread_t xthread; pthread_t xthread;
pthread_mutex_t xmutex; pthread_mutex_t xmutex;
static void catch_usr1(int sig){ static void catch_usr1(int sig) {
(void)sig; (void)sig;
got_usr1 = 1; got_usr1 = 1;
} }
int launch_x(void){ int launch_x(void) {
got_usr1 = 0; got_usr1 = 0;
signal(SIGUSR1, catch_usr1); signal(SIGUSR1, catch_usr1);
/* TODO: Remove hardcoded DISPLAY */ /* TODO: Remove hardcoded DISPLAY */
if((xserver = fork()) == 0){ if((xserver = fork()) == 0) {
signal(SIGUSR1, SIG_IGN); signal(SIGUSR1, SIG_IGN);
execlp("X", "X", ":0", NULL); execlp("X", "X", ":0", NULL);
_exit(-1); _exit(-1);
}else{ } else {
while(!got_usr1){ while(!got_usr1) {
if(waitpid(xserver, NULL, WNOHANG) != 0){ if(waitpid(xserver, NULL, WNOHANG) != 0) {
fprintf(stderr, "server died. what happened?\n"); fprintf(stderr, "server died. what happened?\n");
return 1; return 1;
} }
@ -44,27 +44,27 @@ int launch_x(void){
} }
static int wm_detected = 0; static int wm_detected = 0;
static int wm_detect(Display* disp, XErrorEvent* ev){ static int wm_detect(Display* disp, XErrorEvent* ev) {
(void)disp; (void)disp;
(void)ev; (void)ev;
wm_detected = 1; wm_detected = 1;
} }
static void* x11_thread_routine(void* arg){ static void* x11_thread_routine(void* arg) {
loop_x(); loop_x();
XCloseDisplay(xdisplay); XCloseDisplay(xdisplay);
} }
int init_x(void){ int init_x(void) {
XColor bg; XColor bg;
void* old; void* old;
xdisplay = XOpenDisplay(NULL); xdisplay = XOpenDisplay(NULL);
bg.red = 0x1c * 256; bg.red = 0x1c * 256;
bg.green = 0x45 * 256; bg.green = 0x45 * 256;
bg.blue = 0x73 * 256; bg.blue = 0x73 * 256;
XAllocColor(xdisplay, DefaultColormap(xdisplay, DefaultScreen(xdisplay)), &bg); XAllocColor(xdisplay, DefaultColormap(xdisplay, DefaultScreen(xdisplay)), &bg);
XSetWindowBackground(xdisplay, DefaultRootWindow(xdisplay), bg.pixel); XSetWindowBackground(xdisplay, DefaultRootWindow(xdisplay), bg.pixel);
@ -76,7 +76,7 @@ int init_x(void){
XSync(xdisplay, False); XSync(xdisplay, False);
XSetErrorHandler(old); XSetErrorHandler(old);
if(wm_detected){ if(wm_detected) {
fprintf(stderr, "WM or something seem to be running\n"); fprintf(stderr, "WM or something seem to be running\n");
return 1; return 1;
} }
@ -88,40 +88,40 @@ int init_x(void){
return 0; return 0;
} }
int x_width(void){ int x_width(void) {
return DisplayWidth(xdisplay, DefaultScreen(xdisplay)); return DisplayWidth(xdisplay, DefaultScreen(xdisplay));
} }
int x_height(void){ int x_height(void) {
return DisplayHeight(xdisplay, DefaultScreen(xdisplay)); return DisplayHeight(xdisplay, DefaultScreen(xdisplay));
} }
typedef struct window { typedef struct window {
MwWidget frame; MwWidget frame;
Window client; Window client;
} window_t; } window_t;
void loop_x(void){ void loop_x(void) {
XEvent ev; XEvent ev;
window_t* frames = NULL; window_t* frames = NULL;
while(1){ while(1) {
XNextEvent(xdisplay, &ev); XNextEvent(xdisplay, &ev);
if(ev.type == MapRequest){ if(ev.type == MapRequest) {
XWindowAttributes xwa; XWindowAttributes xwa;
window_t w; window_t w;
int i; int i;
pthread_mutex_lock(&xmutex); pthread_mutex_lock(&xmutex);
for(i = 0; i < arrlen(frames); i++){ for(i = 0; i < arrlen(frames); i++) {
if(frames[i].frame->lowlevel->x11.window == ev.xmaprequest.window){ 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)); XReparentWindow(xdisplay, frames[i].client, frames[i].frame->lowlevel->x11.window, MwDefaultBorderWidth(root), MwDefaultBorderWidth(root));
XMapWindow(xdisplay, ev.xmaprequest.window); XMapWindow(xdisplay, ev.xmaprequest.window);
XMapWindow(xdisplay, frames[i].client); XMapWindow(xdisplay, frames[i].client);
break; break;
} }
} }
if(i != arrlen(frames)){ if(i != arrlen(frames)) {
pthread_mutex_unlock(&xmutex); pthread_mutex_unlock(&xmutex);
continue; continue;
} }
@ -129,22 +129,22 @@ void loop_x(void){
XGetWindowAttributes(xdisplay, ev.xmaprequest.window, &xwa); XGetWindowAttributes(xdisplay, ev.xmaprequest.window, &xwa);
w.client = ev.xmaprequest.window; 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, 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, MwNhasBorder, 1,
MwNinverted, 0, MwNinverted, 0,
NULL); NULL);
XSelectInput(xdisplay, w.client, StructureNotifyMask); XSelectInput(xdisplay, w.client, StructureNotifyMask);
arrput(frames, w); arrput(frames, w);
pthread_mutex_unlock(&xmutex); pthread_mutex_unlock(&xmutex);
}else if(ev.type == UnmapNotify){ } else if(ev.type == UnmapNotify) {
int i; int i;
int c = 0; int c = 0;
pthread_mutex_lock(&xmutex); pthread_mutex_lock(&xmutex);
for(i = 0; i < arrlen(frames); i++){ for(i = 0; i < arrlen(frames); i++) {
if(frames[i].client == ev.xunmap.window){ if(frames[i].client == ev.xunmap.window) {
MwDestroyWidget(frames[i].frame); MwDestroyWidget(frames[i].frame);
arrdel(frames, i); arrdel(frames, i);
c = 1; c = 1;
@ -158,7 +158,7 @@ void loop_x(void){
} }
} }
void kill_x(void){ void kill_x(void) {
kill(xserver, SIGINT); kill(xserver, SIGINT);
waitpid(xserver, NULL, 0); waitpid(xserver, NULL, 0);
} }

View file

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

View file

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