feat: add news ticker with sentiment analysis and error codes to dwn.h and news.h headers

- Add DwnStatus enum with six error codes (OK, ERROR, INVALID_ARG, NO_MEMORY, NOT_FOUND, DISPLAY, IO) to include/dwn.h
- Extend NewsArticle struct with NewsSentiment enum (NEUTRAL, POSITIVE, NEGATIVE) and sentiment_score float field in include/news.h
- Insert DWN_ASSERT and DWN_ASSERT_MSG contract assertion macros plus assert.h include into include/util.h
This commit is contained in:
2025-12-28 03:30:10 +00:00
parent 21e8bcc88a
commit 41fc47abac
42 changed files with 262 additions and 76 deletions
+1
View File
@@ -1,5 +1,6 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* AI Integration implementation
*/
+1
View File
@@ -1,5 +1,6 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Application launcher with .desktop file support
*/
+1
View File
@@ -1,5 +1,6 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* X11 Atoms management implementation
*/
+1
View File
@@ -1,5 +1,6 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Client (window) management implementation
*/
+1
View File
@@ -1,5 +1,6 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Configuration system implementation
*/
+1
View File
@@ -1,5 +1,6 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Window decorations implementation
*/
+41 -3
View File
@@ -1,5 +1,6 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Keyboard shortcut handling implementation
*/
@@ -15,6 +16,11 @@
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <X11/XKBlib.h>
static bool super_pressed = false;
static bool super_used_in_combo = false;
/* Forward declarations for key callbacks */
void key_spawn_terminal(void);
@@ -354,6 +360,17 @@ void keys_grab_all(void)
GrabModeAsync, GrabModeAsync);
}
}
KeyCode super_l = XKeysymToKeycode(dpy, XK_Super_L);
KeyCode super_r = XKeysymToKeycode(dpy, XK_Super_R);
unsigned int lock_mods[] = { 0, Mod2Mask, LockMask, Mod2Mask | LockMask };
for (size_t i = 0; i < sizeof(lock_mods) / sizeof(lock_mods[0]); i++) {
if (super_l) XGrabKey(dpy, super_l, lock_mods[i], root, True,
GrabModeAsync, GrabModeAsync);
if (super_r) XGrabKey(dpy, super_r, lock_mods[i], root, True,
GrabModeAsync, GrabModeAsync);
}
}
void keys_ungrab_all(void)
@@ -412,6 +429,16 @@ void keys_handle_press(XKeyEvent *ev)
KeySym keysym = XLookupKeysym(ev, 0);
if (keysym == XK_Super_L || keysym == XK_Super_R) {
super_pressed = true;
super_used_in_combo = false;
return;
}
if (super_pressed && (ev->state & Mod4Mask)) {
super_used_in_combo = true;
}
/* Clean modifiers (remove NumLock, CapsLock) */
unsigned int clean_mask = ev->state & ~(Mod2Mask | LockMask);
@@ -434,8 +461,19 @@ void keys_handle_press(XKeyEvent *ev)
void keys_handle_release(XKeyEvent *ev)
{
/* Currently no release handling needed */
(void)ev;
if (ev == NULL || dwn == NULL || dwn->display == NULL) {
return;
}
KeySym keysym = XLookupKeysym(ev, 0);
if (keysym == XK_Super_L || keysym == XK_Super_R) {
if (super_pressed && !super_used_in_combo) {
key_spawn_launcher();
}
super_pressed = false;
super_used_in_combo = false;
}
}
/* ========== Default key bindings (XFCE-style) ========== */
@@ -770,7 +808,7 @@ void key_show_shortcuts(void)
const char *shortcuts =
"=== Applications ===\n"
"Ctrl+Alt+T Terminal\n"
"Alt+F2 App launcher\n"
"Super / Alt+F2 App launcher\n"
"Super+E File manager\n"
"Super+B Web browser\n"
"Print Screenshot\n"
+1
View File
@@ -1,5 +1,6 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Layout algorithms implementation
*/
+9 -4
View File
@@ -1,5 +1,6 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Main entry point and event loop
*/
@@ -456,15 +457,19 @@ static void handle_button_press(XButtonEvent *ev)
return;
}
/* Focus on click */
client_focus(c);
/* If click was on client window content, replay the event to the application */
/* If click was on client window content, replay the event to the application FIRST
* before any other X operations. The synchronous grab freezes pointer events until
* XAllowEvents is called. Calling XAllowEvents before client_focus ensures the
* click reaches the application (tabs, buttons, etc.) without timing issues. */
if (is_client_window) {
XAllowEvents(dwn->display, ReplayPointer, ev->time);
client_focus(c);
return;
}
/* Focus on click */
client_focus(c);
/* Check for button clicks in decorations */
if (c->frame != None && ev->window == c->frame) {
ButtonType btn = decorations_hit_test_button(c, ev->x, ev->y);
+41 -1
View File
@@ -1,5 +1,6 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* News ticker implementation
*/
@@ -12,6 +13,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
@@ -24,6 +26,20 @@
/* Fetch interval in milliseconds (5 minutes) */
#define FETCH_INTERVAL 300000
/* Sentiment colors (RGB hex values) */
#define SENTIMENT_COLOR_POSITIVE 0x81C784
#define SENTIMENT_COLOR_NEUTRAL 0xB0BEC5
#define SENTIMENT_COLOR_NEGATIVE 0xE57373
static unsigned long news_sentiment_color(NewsSentiment sentiment)
{
switch (sentiment) {
case SENTIMENT_POSITIVE: return SENTIMENT_COLOR_POSITIVE;
case SENTIMENT_NEGATIVE: return SENTIMENT_COLOR_NEGATIVE;
default: return SENTIMENT_COLOR_NEUTRAL;
}
}
/* Global state */
NewsState news_state = {0};
@@ -225,6 +241,27 @@ static int parse_news_json(const char *json_str)
strncpy(art->author, author->valuestring, sizeof(art->author) - 1);
}
cJSON *sentiment_obj = cJSON_GetObjectItemCaseSensitive(article, "sentiment");
if (cJSON_IsObject(sentiment_obj)) {
cJSON *sentiment_type = cJSON_GetObjectItemCaseSensitive(sentiment_obj, "sentiment");
cJSON *sentiment_score = cJSON_GetObjectItemCaseSensitive(sentiment_obj, "score");
if (cJSON_IsString(sentiment_type) && sentiment_type->valuestring) {
if (strcasecmp(sentiment_type->valuestring, "Positive") == 0) {
art->sentiment = SENTIMENT_POSITIVE;
} else if (strcasecmp(sentiment_type->valuestring, "Negative") == 0) {
art->sentiment = SENTIMENT_NEGATIVE;
} else {
art->sentiment = SENTIMENT_NEUTRAL;
}
}
if (cJSON_IsNumber(sentiment_score)) {
art->sentiment_score = (float)sentiment_score->valuedouble;
}
}
count++;
}
@@ -630,9 +667,12 @@ void news_render(Panel *panel, int x, int max_width, int *used_width)
char display_text[2048];
get_article_display_text(current_article, display_text, sizeof(display_text));
unsigned long article_color = news_sentiment_color(
news_state.articles[current_article].sentiment);
if (draw_x + news_state.display_widths[current_article] > x) {
news_draw_text_clipped(panel->buffer, draw_x, text_y, display_text,
colors->panel_fg, &clip_rect);
article_color, &clip_rect);
}
draw_x += news_state.display_widths[current_article];
+1
View File
@@ -1,5 +1,6 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* D-Bus Notification daemon implementation
*/
+1
View File
@@ -1,5 +1,6 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Panel system implementation
*/
+1
View File
@@ -1,5 +1,6 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* System tray widgets implementation with UTF-8 support
*/
+1
View File
@@ -1,5 +1,6 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Utility functions implementation
*/
+1
View File
@@ -1,5 +1,6 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Workspace management implementation
*/