2025-12-18 11:43:16 +09:00
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
|
|
#include <stb_image.h>
|
2025-12-21 23:34:08 +09:00
|
|
|
#include <stb_ds.h>
|
2025-12-18 11:43:16 +09:00
|
|
|
|
2025-12-21 23:34:08 +09:00
|
|
|
#define _MILSKO
|
2025-12-23 18:21:44 +09:00
|
|
|
#include <MDE/Foundation.h>
|
2025-12-18 11:43:16 +09:00
|
|
|
#include <Mw/Milsko.h>
|
|
|
|
|
#include <libconfig.h>
|
2025-12-23 18:21:44 +09:00
|
|
|
#include <ini.h>
|
|
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
2025-12-23 22:29:18 +09:00
|
|
|
#include <unistd.h>
|
2025-12-18 11:43:16 +09:00
|
|
|
|
|
|
|
|
#define TriW 5
|
|
|
|
|
|
2025-12-21 23:34:08 +09:00
|
|
|
typedef struct opaque {
|
|
|
|
|
MwLLPixmap closed;
|
|
|
|
|
MwLLPixmap opened;
|
|
|
|
|
MwWidget submenu;
|
|
|
|
|
int is_opened;
|
|
|
|
|
MwMenu menu;
|
2025-12-23 18:21:44 +09:00
|
|
|
|
|
|
|
|
char* name;
|
|
|
|
|
char* category;
|
2025-12-23 22:29:18 +09:00
|
|
|
char* exec;
|
2025-12-21 23:34:08 +09:00
|
|
|
} opaque_t;
|
|
|
|
|
|
2025-12-23 18:21:44 +09:00
|
|
|
static int dumper(void* user, const char* section, const char* name, const char* value) {
|
|
|
|
|
opaque_t* opaque = user;
|
|
|
|
|
if(strcmp(section, "Desktop Entry") != 0) return 1;
|
|
|
|
|
|
2025-12-23 22:29:18 +09:00
|
|
|
if(strcmp(name, "Name") == 0) {
|
2025-12-23 18:21:44 +09:00
|
|
|
opaque->name = MwStringDuplicate(value);
|
2025-12-23 22:29:18 +09:00
|
|
|
} else if(strcmp(name, "Categories") == 0) {
|
2025-12-23 18:21:44 +09:00
|
|
|
opaque->category = MwStringDuplicate(value);
|
2025-12-23 22:29:18 +09:00
|
|
|
} else if(strcmp(name, "Exec") == 0) {
|
|
|
|
|
opaque->exec = MwStringDuplicate(value);
|
2025-12-23 18:21:44 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum CATEGORY {
|
|
|
|
|
C_UNKNOWN = 0,
|
|
|
|
|
C_AUDIOVIDEO,
|
|
|
|
|
C_AUDIO,
|
|
|
|
|
C_VIDEO,
|
|
|
|
|
C_DEVELOPMENT,
|
|
|
|
|
C_EDUCATION,
|
|
|
|
|
C_GAME,
|
|
|
|
|
C_GRAPHICS,
|
|
|
|
|
C_NETWORK,
|
|
|
|
|
C_OFFICE,
|
|
|
|
|
C_SCIENCE,
|
|
|
|
|
C_SETTINGS,
|
|
|
|
|
C_SYSTEM,
|
|
|
|
|
C_UTILITY
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const char* category_mapping[] = {
|
2025-12-23 22:29:18 +09:00
|
|
|
"Lost & Found",
|
|
|
|
|
"Multimedia",
|
|
|
|
|
"Audio",
|
|
|
|
|
"Video",
|
|
|
|
|
"Development",
|
|
|
|
|
"Education",
|
|
|
|
|
"Game",
|
|
|
|
|
"Graphics",
|
|
|
|
|
"Network",
|
|
|
|
|
"Office",
|
|
|
|
|
"Science",
|
|
|
|
|
"Settings",
|
|
|
|
|
"System",
|
|
|
|
|
"Utility"};
|
|
|
|
|
|
|
|
|
|
static void call(const char* name, int dir, int symlink, void* user) {
|
2025-12-23 18:21:44 +09:00
|
|
|
opaque_t* opaque = user;
|
2025-12-23 22:29:18 +09:00
|
|
|
int c = C_UNKNOWN;
|
2025-12-23 18:21:44 +09:00
|
|
|
|
2025-12-23 22:29:18 +09:00
|
|
|
opaque->name = NULL;
|
2025-12-23 18:21:44 +09:00
|
|
|
opaque->category = NULL;
|
2025-12-23 22:29:18 +09:00
|
|
|
opaque->exec = NULL;
|
2025-12-23 18:21:44 +09:00
|
|
|
ini_parse(name, dumper, user);
|
|
|
|
|
|
2025-12-23 22:29:18 +09:00
|
|
|
if(opaque->category != NULL) {
|
2025-12-23 18:21:44 +09:00
|
|
|
char* str = opaque->category;
|
|
|
|
|
|
2025-12-23 22:29:18 +09:00
|
|
|
while(str != NULL) {
|
2025-12-23 18:21:44 +09:00
|
|
|
char* s = strchr(str, ';');
|
|
|
|
|
|
2025-12-23 22:29:18 +09:00
|
|
|
if(s != NULL) {
|
2025-12-23 18:21:44 +09:00
|
|
|
s[0] = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:29:18 +09:00
|
|
|
if(strcasecmp(str, "AudioVideo") == 0) {
|
2025-12-23 18:21:44 +09:00
|
|
|
c = C_AUDIOVIDEO;
|
2025-12-23 22:29:18 +09:00
|
|
|
} else if(strcasecmp(str, "Audio") == 0) {
|
2025-12-23 18:21:44 +09:00
|
|
|
c = C_AUDIO;
|
2025-12-23 22:29:18 +09:00
|
|
|
} else if(strcasecmp(str, "Video") == 0) {
|
2025-12-23 18:21:44 +09:00
|
|
|
c = C_VIDEO;
|
2025-12-23 22:29:18 +09:00
|
|
|
} else if(strcasecmp(str, "Development") == 0) {
|
2025-12-23 18:21:44 +09:00
|
|
|
c = C_DEVELOPMENT;
|
2025-12-23 22:29:18 +09:00
|
|
|
} else if(strcasecmp(str, "Education") == 0) {
|
2025-12-23 18:21:44 +09:00
|
|
|
c = C_EDUCATION;
|
2025-12-23 22:29:18 +09:00
|
|
|
} else if(strcasecmp(str, "Game") == 0) {
|
2025-12-23 18:21:44 +09:00
|
|
|
c = C_GAME;
|
2025-12-23 22:29:18 +09:00
|
|
|
} else if(strcasecmp(str, "Graphics") == 0) {
|
2025-12-23 18:21:44 +09:00
|
|
|
c = C_GRAPHICS;
|
2025-12-23 22:29:18 +09:00
|
|
|
} else if(strcasecmp(str, "Network") == 0) {
|
2025-12-23 18:21:44 +09:00
|
|
|
c = C_NETWORK;
|
2025-12-23 22:29:18 +09:00
|
|
|
} else if(strcasecmp(str, "Office") == 0) {
|
2025-12-23 18:21:44 +09:00
|
|
|
c = C_OFFICE;
|
2025-12-23 22:29:18 +09:00
|
|
|
} else if(strcasecmp(str, "Science") == 0) {
|
2025-12-23 18:21:44 +09:00
|
|
|
c = C_SCIENCE;
|
2025-12-23 22:29:18 +09:00
|
|
|
} else if(strcasecmp(str, "Settings") == 0) {
|
2025-12-23 18:21:44 +09:00
|
|
|
c = C_SETTINGS;
|
2025-12-23 22:29:18 +09:00
|
|
|
} else if(strcasecmp(str, "System") == 0) {
|
2025-12-23 18:21:44 +09:00
|
|
|
c = C_SYSTEM;
|
2025-12-23 22:29:18 +09:00
|
|
|
} else if(strcasecmp(str, "Utility") == 0) {
|
2025-12-23 18:21:44 +09:00
|
|
|
c = C_UTILITY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
str = s;
|
|
|
|
|
if(str != NULL) str++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free(opaque->category);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:29:18 +09:00
|
|
|
if(opaque->name != NULL) {
|
|
|
|
|
int i;
|
2025-12-23 18:21:44 +09:00
|
|
|
MwMenu mc = NULL, m;
|
2025-12-23 22:29:18 +09:00
|
|
|
for(i = 0; i < arrlen(opaque->menu->sub); i++) {
|
|
|
|
|
if(strcmp(opaque->menu->sub[i]->name, category_mapping[c]) == 0) {
|
2025-12-23 18:21:44 +09:00
|
|
|
mc = opaque->menu->sub[i];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-23 22:29:18 +09:00
|
|
|
if(mc == NULL) {
|
|
|
|
|
mc = malloc(sizeof(*mc));
|
2025-12-23 18:21:44 +09:00
|
|
|
mc->name = (char*)category_mapping[c];
|
|
|
|
|
mc->keep = 0;
|
2025-12-23 22:29:18 +09:00
|
|
|
mc->sub = NULL;
|
2025-12-23 18:21:44 +09:00
|
|
|
mc->wsub = NULL;
|
|
|
|
|
|
|
|
|
|
arrput(opaque->menu->sub, mc);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:29:18 +09:00
|
|
|
for(i = 0; i < arrlen(mc->sub); i++) {
|
|
|
|
|
if(strcmp(mc->sub[i]->name, opaque->name) == 0) {
|
2025-12-23 18:21:44 +09:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:29:18 +09:00
|
|
|
if(i == arrlen(mc->sub)) {
|
|
|
|
|
m = malloc(sizeof(*m));
|
|
|
|
|
m->name = opaque->name;
|
|
|
|
|
m->keep = 0;
|
|
|
|
|
m->sub = NULL;
|
|
|
|
|
m->wsub = NULL;
|
|
|
|
|
m->opaque = opaque->exec;
|
2025-12-23 18:21:44 +09:00
|
|
|
|
|
|
|
|
arrput(mc->sub, m);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:29:18 +09:00
|
|
|
static char** desktop_exec(const char* exec, const char* file) {
|
|
|
|
|
char** a = NULL;
|
|
|
|
|
int i;
|
|
|
|
|
char* buf = malloc(1);
|
|
|
|
|
|
|
|
|
|
buf[0] = 0;
|
|
|
|
|
|
|
|
|
|
for(i = 0;; i++) {
|
|
|
|
|
if(exec[i] == ' ' || exec[i] == 0) {
|
|
|
|
|
if(strlen(buf) > 0) arrput(a, buf);
|
|
|
|
|
|
|
|
|
|
buf = malloc(1);
|
|
|
|
|
buf[0] = 0;
|
|
|
|
|
|
|
|
|
|
if(exec[i] == 0) break;
|
|
|
|
|
} else if(exec[i] == '%') {
|
|
|
|
|
char c = exec[++i];
|
|
|
|
|
|
|
|
|
|
if(c == 'f' && file != NULL) {
|
|
|
|
|
char* old = buf;
|
|
|
|
|
|
|
|
|
|
buf = malloc(strlen(old) + strlen(file) + 1);
|
|
|
|
|
strcpy(buf, old);
|
|
|
|
|
strcpy(buf + strlen(old), file);
|
|
|
|
|
|
|
|
|
|
free(old);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
char* old = buf;
|
|
|
|
|
|
|
|
|
|
buf = malloc(strlen(old) + 2);
|
|
|
|
|
strcpy(buf, old);
|
|
|
|
|
buf[strlen(old)] = exec[i];
|
|
|
|
|
buf[strlen(old) + 1] = 0;
|
|
|
|
|
|
|
|
|
|
free(old);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
arrput(a, NULL);
|
|
|
|
|
|
|
|
|
|
return a;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-21 23:34:08 +09:00
|
|
|
static void menu(MwWidget handle, void* user, void* client) {
|
|
|
|
|
opaque_t* opaque = handle->opaque;
|
2025-12-23 22:29:18 +09:00
|
|
|
char* s = NULL;
|
|
|
|
|
MwMenu m = client;
|
|
|
|
|
|
|
|
|
|
if(m->opaque != NULL) {
|
|
|
|
|
char** args = desktop_exec(m->opaque, NULL);
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
if(fork() == 0) {
|
|
|
|
|
execvp(args[0], args);
|
|
|
|
|
|
|
|
|
|
_exit(-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(i = 0; i < arrlen(args); i++) {
|
|
|
|
|
if(args[i] != NULL) free(args[i]);
|
|
|
|
|
}
|
|
|
|
|
arrfree(args);
|
|
|
|
|
}
|
2025-12-21 23:34:08 +09:00
|
|
|
|
|
|
|
|
if(opaque->is_opened) {
|
|
|
|
|
opaque->is_opened = 0;
|
|
|
|
|
|
|
|
|
|
opaque->submenu = NULL;
|
|
|
|
|
|
|
|
|
|
MwVaApply(handle,
|
|
|
|
|
MwNpixmap, opaque->closed,
|
|
|
|
|
MwNforceInverted, 0,
|
|
|
|
|
NULL);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void activate(MwWidget handle, void* user, void* client) {
|
|
|
|
|
opaque_t* opaque = handle->opaque;
|
|
|
|
|
|
|
|
|
|
opaque->is_opened = opaque->is_opened ? 0 : 1;
|
|
|
|
|
if(opaque->is_opened) {
|
|
|
|
|
MwPoint p;
|
|
|
|
|
|
|
|
|
|
MwVaApply(handle,
|
|
|
|
|
MwNpixmap, opaque->opened,
|
|
|
|
|
MwNforceInverted, 1,
|
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
|
|
opaque->submenu = MwCreateWidget(MwSubMenuClass, "submenu", handle, 0, MwGetInteger(handle, MwNheight), 0, 0);
|
|
|
|
|
|
|
|
|
|
p.x = 0;
|
|
|
|
|
p.y = 0;
|
|
|
|
|
MwSubMenuAppear(opaque->submenu, opaque->menu, &p, 1);
|
|
|
|
|
} else {
|
|
|
|
|
MwVaApply(handle,
|
|
|
|
|
MwNpixmap, opaque->closed,
|
|
|
|
|
MwNforceInverted, 0,
|
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
|
|
if(opaque->submenu != NULL) MwDestroyWidget(opaque->submenu);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:29:18 +09:00
|
|
|
int sort_alphabet(const void* a, const void* b) {
|
2025-12-23 18:21:44 +09:00
|
|
|
return strcmp((*((MwMenu*)a))->name, (*((MwMenu*)b))->name);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-18 11:43:16 +09:00
|
|
|
void module(MwWidget box, config_setting_t* setting) {
|
|
|
|
|
MwLLPixmap closed = NULL;
|
|
|
|
|
MwLLPixmap opened = NULL;
|
2025-12-21 23:34:08 +09:00
|
|
|
MwWidget btn;
|
2025-12-18 11:43:16 +09:00
|
|
|
int w, h;
|
|
|
|
|
unsigned int ch;
|
|
|
|
|
unsigned char* rgb;
|
2025-12-21 23:34:08 +09:00
|
|
|
opaque_t* opaque = malloc(sizeof(*opaque));
|
|
|
|
|
MwMenu m;
|
2025-12-23 22:29:18 +09:00
|
|
|
int i;
|
2025-12-18 11:43:16 +09:00
|
|
|
|
|
|
|
|
if((rgb = stbi_load(ICON64DIR "/logo.png", &w, &h, &ch, 4)) != NULL) {
|
|
|
|
|
int bw = MwGetInteger(box, MwNheight) - MwDefaultBorderWidth(box) * 2;
|
|
|
|
|
unsigned char* data = malloc(bw * bw * 4);
|
|
|
|
|
unsigned char* save = malloc(bw * bw * 4);
|
|
|
|
|
int y, x;
|
|
|
|
|
int max = (TriW - 1) / 2;
|
|
|
|
|
|
|
|
|
|
memset(data, 0, bw * bw * 4);
|
|
|
|
|
for(y = 0; y < h; y++) {
|
|
|
|
|
int fy = y * bw / h;
|
|
|
|
|
for(x = 0; x < w; x++) {
|
|
|
|
|
int fx = x * bw / w;
|
|
|
|
|
memcpy(&data[(fy * bw + fx) * 4], &rgb[(y * w + x) * 4], 4);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
memcpy(save, data, bw * bw * 4);
|
|
|
|
|
|
|
|
|
|
for(y = 0; y < max + 1; y++) {
|
|
|
|
|
int tw = y * 2 + 1;
|
|
|
|
|
int ix = w - TriW + (max - y);
|
|
|
|
|
|
|
|
|
|
for(x = 0; x < tw; x++) {
|
|
|
|
|
data[4 * (y * bw + x + ix) + 3] = 255;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
closed = MwLoadRaw(box, data, bw, bw);
|
|
|
|
|
|
|
|
|
|
memcpy(data, save, bw * bw * 4);
|
|
|
|
|
|
|
|
|
|
max = (TriW - 1) / 2;
|
|
|
|
|
for(y = 0; y < max + 1; y++) {
|
|
|
|
|
int tw = (max - y) * 2 + 1;
|
|
|
|
|
int ix = w - TriW + y;
|
|
|
|
|
|
|
|
|
|
for(x = 0; x < tw; x++) {
|
|
|
|
|
data[4 * (y * bw + x + ix) + 3] = 255;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
opened = MwLoadRaw(box, data, bw, bw);
|
|
|
|
|
|
|
|
|
|
free(save);
|
|
|
|
|
free(data);
|
|
|
|
|
free(rgb);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-21 23:34:08 +09:00
|
|
|
btn = MwVaCreateWidget(MwButtonClass, "startbutton", box, 0, 0, 0, 0,
|
|
|
|
|
MwNfixedSize, MwGetInteger(box, MwNheight),
|
|
|
|
|
MwNflat, 1,
|
|
|
|
|
MwNpixmap, closed,
|
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
|
|
opaque->closed = closed;
|
|
|
|
|
opaque->opened = opened;
|
|
|
|
|
opaque->submenu = NULL;
|
|
|
|
|
opaque->is_opened = 0;
|
|
|
|
|
|
|
|
|
|
opaque->menu = malloc(sizeof(*opaque->menu));
|
|
|
|
|
opaque->menu->name = NULL;
|
|
|
|
|
opaque->menu->keep = 0;
|
|
|
|
|
opaque->menu->wsub = NULL;
|
|
|
|
|
opaque->menu->sub = NULL;
|
|
|
|
|
|
2025-12-23 18:21:44 +09:00
|
|
|
MDEDirectoryScan(DATAROOTDIR "/applications", call, opaque);
|
|
|
|
|
MDEDirectoryScan("/usr/share/applications", call, opaque);
|
2025-12-23 22:29:18 +09:00
|
|
|
#if defined(__NetBSD__)
|
|
|
|
|
MDEDirectoryScan("/usr/pkg/share/applications", call, opaque);
|
|
|
|
|
#endif
|
2025-12-23 18:21:44 +09:00
|
|
|
qsort(opaque->menu->sub, arrlen(opaque->menu->sub), sizeof(MwMenu), sort_alphabet);
|
|
|
|
|
|
2025-12-23 22:29:18 +09:00
|
|
|
for(i = 0; i < arrlen(opaque->menu->sub); i++) {
|
2025-12-23 18:21:44 +09:00
|
|
|
qsort(opaque->menu->sub[i]->sub, arrlen(opaque->menu->sub[i]->sub), sizeof(MwMenu), sort_alphabet);
|
2025-12-21 23:34:08 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
btn->opaque = opaque;
|
|
|
|
|
|
|
|
|
|
MwAddUserHandler(btn, MwNactivateHandler, activate, NULL);
|
|
|
|
|
MwAddUserHandler(btn, MwNmenuHandler, menu, NULL);
|
2025-12-18 11:43:16 +09:00
|
|
|
}
|