chore: update c, d, h files

This commit is contained in:
retoor 2026-01-08 23:58:19 +01:00
parent 250e3e2978
commit f64aedc1fc
16 changed files with 20963 additions and 6 deletions

BIN
bin/dwn

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -20,7 +20,7 @@ build/main.o: src/main.c include/dwn.h include/config.h include/dwn.h \
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
/usr/include/dbus-1.0/dbus/dbus-threads.h include/systray.h \
include/news.h include/applauncher.h include/ai.h include/autostart.h \
include/demo.h include/util.h
include/services.h include/api.h include/demo.h include/util.h
include/dwn.h:
include/config.h:
include/dwn.h:
@ -55,5 +55,7 @@ include/news.h:
include/applauncher.h:
include/ai.h:
include/autostart.h:
include/services.h:
include/api.h:
include/demo.h:
include/util.h:

Binary file not shown.

16
include/api.h Normal file
View File

@ -0,0 +1,16 @@
/*
* DWN - Desktop Window Manager
* WebSocket API
*/
#ifndef DWN_API_H
#define DWN_API_H
#include <stdbool.h>
void api_init(void);
void api_process(void);
void api_cleanup(void);
void api_handle_json_command(const char *json_str);
#endif

View File

@ -60,6 +60,10 @@ struct Config {
bool autostart_xdg;
char autostart_path[512];
char services_path[512];
bool api_enabled;
int api_port;
int demo_step_delay_ms;
int demo_ai_timeout_ms;
int demo_window_timeout_ms;

3013
include/mongoose.h Normal file

File diff suppressed because it is too large Load Diff

13
include/services.h Normal file
View File

@ -0,0 +1,13 @@
/*
* DWN - Desktop Window Manager
* Service management
*/
#ifndef DWN_SERVICES_H
#define DWN_SERVICES_H
void services_init(void);
void services_run(void);
void services_cleanup(void);
#endif

View File

@ -390,6 +390,32 @@ static void ai_command_response_callback(AIRequest *req)
}
if (req->state == AI_STATE_COMPLETED && req->response != NULL) {
char *wm_cmd = strstr(req->response, "[WM_CMD:");
if (wm_cmd != NULL) {
char *cmd_start = strchr(wm_cmd, ':');
if (cmd_start != NULL) {
cmd_start++;
while (*cmd_start == ' ') cmd_start++;
char *cmd_end = strchr(cmd_start, ']');
if (cmd_end != NULL) {
size_t cmd_len = cmd_end - cmd_start;
char *cmd_json = dwn_malloc(cmd_len + 1);
strncpy(cmd_json, cmd_start, cmd_len);
cmd_json[cmd_len] = '\0';
LOG_INFO("AI executing WM command: %s", cmd_json);
notification_show("DWN AI", "Executing WM Command", cmd_json, NULL, 2000);
/* Forward to our new API handler logic */
extern void api_handle_json_command(const char *json_str);
api_handle_json_command(cmd_json);
dwn_free(cmd_json);
}
}
}
char *run_cmd = strstr(req->response, "[RUN:");
if (run_cmd == NULL) {
run_cmd = strstr(req->response, "[EXEC:");
@ -471,13 +497,16 @@ void ai_show_command_palette(void)
"You can execute shell commands for the user.\n\n"
"IMPORTANT: When the user asks you to run, open, launch, or start an application, "
"respond with the command in this exact format: [RUN: command]\n"
"When the user asks for window management tasks (switching workspaces, focusing windows), "
"respond with the JSON command in this exact format: [WM_CMD: {\"command\": \"...\", ...}]\n"
"Available WM Commands:\n"
"- switch_workspace (e.g. [WM_CMD: {\"command\": \"switch_workspace\", \"workspace\": 2}])\n"
"- focus_client (e.g. [WM_CMD: {\"command\": \"focus_client\", \"window\": <window_id>}])\n"
"Examples:\n"
"- User: 'open chrome' -> [RUN: google-chrome]\n"
"- User: 'run firefox' -> [RUN: firefox]\n"
"- User: 'open file manager' -> [RUN: thunar]\n"
"- User: 'launch terminal' -> [RUN: xfce4-terminal]\n"
"- User: 'open vs code' -> [RUN: code]\n\n"
"For questions or non-command requests, respond briefly (1-2 sentences) without the [RUN:] format.\n\n"
"- User: 'switch to workspace 3' -> [WM_CMD: {\"command\": \"switch_workspace\", \"workspace\": 2}]\n"
"- User: 'go to desktop 1' -> [WM_CMD: {\"command\": \"switch_workspace\", \"workspace\": 0}]\n"
"For questions or non-command requests, respond briefly (1-2 sentences) without the [RUN:] or [WM_CMD:] format.\n\n"
"User's current task: %s\n"
"User's request: %s",
task, input);

171
src/api.c Normal file
View File

@ -0,0 +1,171 @@
/*
* DWN - Desktop Window Manager
* WebSocket API implementation using Mongoose and cJSON
*/
#include "api.h"
#include "dwn.h"
#include "config.h"
#include "util.h"
#include "workspace.h"
#include "client.h"
#include "cJSON.h"
#include "mongoose.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static struct mg_mgr mgr;
static bool initialized = false;
static void handle_get_status(struct mg_connection *c) {
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "status", "ok");
cJSON_AddStringToObject(root, "version", DWN_VERSION);
cJSON_AddNumberToObject(root, "current_workspace", dwn->current_workspace);
cJSON_AddNumberToObject(root, "client_count", dwn->client_count);
char *json = cJSON_PrintUnformatted(root);
if (c) mg_ws_send(c, json, strlen(json), WEBSOCKET_OP_TEXT);
free(json);
cJSON_Delete(root);
}
static void handle_get_workspaces(struct mg_connection *c) {
cJSON *root = cJSON_CreateArray();
for (int i = 0; i < MAX_WORKSPACES; i++) {
cJSON *ws = cJSON_CreateObject();
cJSON_AddNumberToObject(ws, "id", i);
cJSON_AddStringToObject(ws, "name", dwn->workspaces[i].name);
cJSON_AddNumberToObject(ws, "layout", (double)dwn->workspaces[i].layout);
int clients_on_ws = 0;
for (Client *cl = dwn->client_list; cl; cl = cl->next) {
if (cl->workspace == (unsigned int)i) clients_on_ws++;
}
cJSON_AddNumberToObject(ws, "client_count", clients_on_ws);
cJSON_AddItemToArray(root, ws);
}
char *json = cJSON_PrintUnformatted(root);
if (c) mg_ws_send(c, json, strlen(json), WEBSOCKET_OP_TEXT);
free(json);
cJSON_Delete(root);
}
static void handle_get_clients(struct mg_connection *c) {
cJSON *root = cJSON_CreateArray();
for (Client *cl = dwn->client_list; cl; cl = cl->next) {
cJSON *item = cJSON_CreateObject();
cJSON_AddNumberToObject(item, "window", (double)cl->window);
cJSON_AddStringToObject(item, "title", cl->title);
cJSON_AddStringToObject(item, "class", cl->class);
cJSON_AddNumberToObject(item, "workspace", (double)cl->workspace);
cJSON_AddNumberToObject(item, "x", (double)cl->x);
cJSON_AddNumberToObject(item, "y", (double)cl->y);
cJSON_AddNumberToObject(item, "width", (double)cl->width);
cJSON_AddNumberToObject(item, "height", (double)cl->height);
cJSON_AddBoolToObject(item, "focused", (workspace_get_current() && workspace_get_current()->focused == cl));
cJSON_AddItemToArray(root, item);
}
char *json = cJSON_PrintUnformatted(root);
if (c) mg_ws_send(c, json, strlen(json), WEBSOCKET_OP_TEXT);
free(json);
cJSON_Delete(root);
}
static void handle_command(struct mg_connection *c, cJSON *json) {
cJSON *cmd = cJSON_GetObjectItem(json, "command");
if (!cJSON_IsString(cmd)) return;
if (strcmp(cmd->valuestring, "get_status") == 0) {
handle_get_status(c);
} else if (strcmp(cmd->valuestring, "get_workspaces") == 0) {
handle_get_workspaces(c);
} else if (strcmp(cmd->valuestring, "get_clients") == 0) {
handle_get_clients(c);
} else if (strcmp(cmd->valuestring, "switch_workspace") == 0) {
cJSON *id = cJSON_GetObjectItem(json, "workspace");
if (cJSON_IsNumber(id)) {
workspace_switch(id->valueint);
handle_get_status(c);
}
} else if (strcmp(cmd->valuestring, "run_command") == 0) {
cJSON *exec = cJSON_GetObjectItem(json, "exec");
if (cJSON_IsString(exec)) {
spawn_async(exec->valuestring);
cJSON *resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "status", "launched");
char *resp_json = cJSON_PrintUnformatted(resp);
mg_ws_send(c, resp_json, strlen(resp_json), WEBSOCKET_OP_TEXT);
free(resp_json);
cJSON_Delete(resp);
}
} else if (strcmp(cmd->valuestring, "focus_client") == 0) {
cJSON *win = cJSON_GetObjectItem(json, "window");
if (cJSON_IsNumber(win)) {
Client *cl = client_find_by_window((Window)win->valuedouble);
if (cl) {
if (cl->workspace != (unsigned int)dwn->current_workspace) {
workspace_switch(cl->workspace);
}
client_focus(cl);
handle_get_status(c);
}
}
}
}
static void fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_WS_MSG) {
struct mg_ws_message *wm = (struct mg_ws_message *) ev_data;
cJSON *json = cJSON_ParseWithLength(wm->data.buf, wm->data.len);
if (json) {
handle_command(c, json);
cJSON_Delete(json);
}
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ws"), NULL)) {
mg_ws_upgrade(c, hm, NULL);
} else {
mg_http_reply(c, 200, "", "DWN WebSocket API is running at /ws\n");
}
}
}
void api_handle_json_command(const char *json_str) {
cJSON *json = cJSON_Parse(json_str);
if (json) {
handle_command(NULL, json);
cJSON_Delete(json);
}
}
void api_init(void) {
if (!dwn->config->api_enabled) return;
mg_mgr_init(&mgr);
char addr[64];
snprintf(addr, sizeof(addr), "http://0.0.0.0:%d", dwn->config->api_port);
if (mg_http_listen(&mgr, addr, fn, NULL) == NULL) {
LOG_ERROR("Failed to start API server on %s", addr);
return;
}
LOG_INFO("API server started on %s", addr);
initialized = true;
}
void api_process(void) {
if (!initialized) return;
mg_mgr_poll(&mgr, 0); // Non-blocking poll
}
void api_cleanup(void) {
if (!initialized) return;
mg_mgr_free(&mgr);
initialized = false;
}

View File

@ -85,6 +85,14 @@ void config_set_defaults(Config *cfg)
dwn_free(autostart_path);
}
char *services_path = expand_path("~/.config/dwn/services.d");
if (services_path != NULL) {
strncpy(cfg->services_path, services_path, sizeof(cfg->services_path) - 1);
dwn_free(services_path);
}
cfg->api_enabled = true;
cfg->api_port = 8777;
cfg->demo_step_delay_ms = 4000;
cfg->demo_ai_timeout_ms = 15000;
cfg->demo_window_timeout_ms = 5000;
@ -180,6 +188,20 @@ static void handle_config_entry(const char *section, const char *key,
dwn_free(expanded);
}
}
} else if (strcmp(section, "services") == 0) {
if (strcmp(key, "path") == 0) {
char *expanded = expand_path(value);
if (expanded != NULL) {
strncpy(cfg->services_path, expanded, sizeof(cfg->services_path) - 1);
dwn_free(expanded);
}
}
} else if (strcmp(section, "api") == 0) {
if (strcmp(key, "enabled") == 0) {
cfg->api_enabled = (strcmp(value, "true") == 0 || strcmp(value, "1") == 0);
} else if (strcmp(key, "port") == 0) {
cfg->api_port = atoi(value);
}
} else if (strcmp(section, "demo") == 0) {
if (strcmp(key, "step_delay") == 0) {
int val = atoi(value);

View File

@ -19,6 +19,8 @@
#include "applauncher.h"
#include "ai.h"
#include "autostart.h"
#include "services.h"
#include "api.h"
#include "demo.h"
#include "util.h"
@ -777,12 +779,17 @@ int dwn_init(void)
autostart_init();
autostart_run();
services_init();
services_run();
keys_init();
notifications_init();
ai_init();
api_init();
demo_init();
atoms_setup_ewmh();
@ -813,10 +820,12 @@ void dwn_cleanup(void)
LOG_INFO("DWN shutting down");
demo_cleanup();
api_cleanup();
ai_cleanup();
notifications_cleanup();
news_cleanup();
applauncher_cleanup();
services_cleanup();
autostart_cleanup();
keys_cleanup();
xembed_cleanup();
@ -872,6 +881,8 @@ void dwn_run(void)
notifications_process_messages();
api_process();
ai_process_pending();
exa_process_pending();

17594
src/mongoose.c Normal file

File diff suppressed because it is too large Load Diff

82
src/services.c Normal file
View File

@ -0,0 +1,82 @@
/*
* DWN - Desktop Window Manager
* Service management implementation
*/
#include "services.h"
#include "dwn.h"
#include "config.h"
#include "util.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <limits.h>
void services_init(void)
{
if (dwn == NULL || dwn->config == NULL) {
return;
}
if (dwn->config->services_path[0] != '\0') {
struct stat st;
if (stat(dwn->config->services_path, &st) != 0) {
if (mkdir(dwn->config->services_path, 0755) == 0) {
LOG_INFO("Created services directory: %s", dwn->config->services_path);
}
}
}
LOG_INFO("Services initialized");
}
void services_run(void)
{
if (dwn == NULL || dwn->config == NULL) {
return;
}
if (dwn->config->services_path[0] == '\0') {
return;
}
DIR *dir = opendir(dwn->config->services_path);
if (dir == NULL) {
LOG_DEBUG("Cannot open services directory: %s", dwn->config->services_path);
return;
}
LOG_INFO("Running services from: %s", dwn->config->services_path);
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.') {
continue;
}
char full_path[PATH_MAX];
int ret = snprintf(full_path, sizeof(full_path), "%s/%s", dwn->config->services_path, entry->d_name);
if (ret < 0 || (size_t)ret >= sizeof(full_path)) {
continue;
}
if (access(full_path, X_OK) != 0) {
LOG_DEBUG("Skipping non-executable service: %s", entry->d_name);
continue;
}
LOG_INFO("Starting service: %s", entry->d_name);
spawn_async(full_path);
}
closedir(dir);
}
void services_cleanup(void)
{
LOG_DEBUG("Services cleanup");
}