diff --git a/bin/dwn b/bin/dwn
index 1a1b4c0..9f55182 100755
Binary files a/bin/dwn and b/bin/dwn differ
diff --git a/build/ai.o b/build/ai.o
index b176314..e1eb3b9 100644
Binary files a/build/ai.o and b/build/ai.o differ
diff --git a/build/atoms.o b/build/atoms.o
index 92f6d58..b600c15 100644
Binary files a/build/atoms.o and b/build/atoms.o differ
diff --git a/build/autostart.o b/build/autostart.o
index b31f73a..f49b273 100644
Binary files a/build/autostart.o and b/build/autostart.o differ
diff --git a/build/client.d b/build/client.d
index 45f07aa..5c1a493 100644
--- a/build/client.d
+++ b/build/client.d
@@ -18,7 +18,7 @@ build/client.o: src/client.c include/client.h include/dwn.h \
/usr/include/dbus-1.0/dbus/dbus-server.h \
/usr/include/dbus-1.0/dbus/dbus-signature.h \
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
- /usr/include/dbus-1.0/dbus/dbus-threads.h
+ /usr/include/dbus-1.0/dbus/dbus-threads.h include/layout.h
include/client.h:
include/dwn.h:
include/atoms.h:
@@ -45,3 +45,4 @@ include/notifications.h:
/usr/include/dbus-1.0/dbus/dbus-signature.h:
/usr/include/dbus-1.0/dbus/dbus-syntax.h:
/usr/include/dbus-1.0/dbus/dbus-threads.h:
+include/layout.h:
diff --git a/build/client.o b/build/client.o
index 7f284f0..a1527ff 100644
Binary files a/build/client.o and b/build/client.o differ
diff --git a/build/config.o b/build/config.o
index ddc2281..ff378ee 100644
Binary files a/build/config.o and b/build/config.o differ
diff --git a/build/decorations.o b/build/decorations.o
index 47ec16a..ccfd53d 100644
Binary files a/build/decorations.o and b/build/decorations.o differ
diff --git a/build/demo.o b/build/demo.o
index 6ff6f6b..42c967e 100644
Binary files a/build/demo.o and b/build/demo.o differ
diff --git a/build/keys.o b/build/keys.o
index 4e9c12b..2b7143a 100644
Binary files a/build/keys.o and b/build/keys.o differ
diff --git a/build/layout.o b/build/layout.o
index f1dda5a..b04a777 100644
Binary files a/build/layout.o and b/build/layout.o differ
diff --git a/build/main.o b/build/main.o
index 261813d..cbd778c 100644
Binary files a/build/main.o and b/build/main.o differ
diff --git a/build/news.o b/build/news.o
index a414091..2b187e2 100644
Binary files a/build/news.o and b/build/news.o differ
diff --git a/build/notifications.o b/build/notifications.o
index 4dc1e10..8496e2f 100644
Binary files a/build/notifications.o and b/build/notifications.o differ
diff --git a/build/panel.o b/build/panel.o
index 6cc1a11..dcd7b5c 100644
Binary files a/build/panel.o and b/build/panel.o differ
diff --git a/build/systray.o b/build/systray.o
index 8b8e820..6c30bb1 100644
Binary files a/build/systray.o and b/build/systray.o differ
diff --git a/build/util.o b/build/util.o
index 5f6514e..36c03a1 100644
Binary files a/build/util.o and b/build/util.o differ
diff --git a/build/workspace.o b/build/workspace.o
index 3a772ae..b69a31a 100644
Binary files a/build/workspace.o and b/build/workspace.o differ
diff --git a/include/client.h b/include/client.h
index 6fa1a81..6e79f02 100644
--- a/include/client.h
+++ b/include/client.h
@@ -35,11 +35,14 @@ void client_update_title(Client *client);
void client_update_class(Client *client);
void client_set_fullscreen(Client *client, bool fullscreen);
void client_toggle_fullscreen(Client *client);
+void client_set_maximize(Client *client, bool maximized);
+void client_toggle_maximize(Client *client);
void client_set_floating(Client *client, bool floating);
void client_toggle_floating(Client *client);
bool client_is_floating(Client *client);
bool client_is_fullscreen(Client *client);
+bool client_is_maximized(Client *client);
bool client_is_minimized(Client *client);
bool client_is_dialog(Window window);
bool client_is_dock(Window window);
diff --git a/include/config.h b/include/config.h
index b7d4ba6..2ae34a5 100644
--- a/include/config.h
+++ b/include/config.h
@@ -31,6 +31,7 @@ struct Config {
char launcher[128];
char file_manager[128];
FocusMode focus_mode;
+ int focus_follow_delay_ms;
bool show_decorations;
int border_width;
@@ -58,6 +59,10 @@ struct Config {
bool autostart_enabled;
bool autostart_xdg;
char autostart_path[512];
+
+ int demo_step_delay_ms;
+ int demo_ai_timeout_ms;
+ int demo_window_timeout_ms;
};
Config *config_create(void);
diff --git a/include/demo.h b/include/demo.h
index d2de378..faa5557 100644
--- a/include/demo.h
+++ b/include/demo.h
@@ -23,6 +23,14 @@ typedef enum {
DEMO_COMPLETE
} DemoPhase;
+typedef enum {
+ DEMO_WAIT_NONE,
+ DEMO_WAIT_TIME,
+ DEMO_WAIT_WINDOW_SPAWN,
+ DEMO_WAIT_AI_RESPONSE,
+ DEMO_WAIT_EXA_RESPONSE
+} DemoWaitCondition;
+
void demo_init(void);
void demo_cleanup(void);
void demo_start(void);
diff --git a/include/dwn.h b/include/dwn.h
index acddbda..df7983e 100644
--- a/include/dwn.h
+++ b/include/dwn.h
@@ -58,7 +58,8 @@ typedef enum {
CLIENT_FULLSCREEN = (1 << 1),
CLIENT_URGENT = (1 << 2),
CLIENT_MINIMIZED = (1 << 3),
- CLIENT_STICKY = (1 << 4)
+ CLIENT_STICKY = (1 << 4),
+ CLIENT_MAXIMIZED = (1 << 5)
} ClientFlags;
typedef struct Client Client;
@@ -81,6 +82,8 @@ struct Client {
char class[64];
Client *next;
Client *prev;
+ Client *mru_next;
+ Client *mru_prev;
};
struct Monitor {
@@ -93,6 +96,8 @@ struct Monitor {
struct Workspace {
Client *clients;
Client *focused;
+ Client *mru_head;
+ Client *mru_tail;
LayoutType layout;
float master_ratio;
int master_count;
@@ -142,6 +147,9 @@ typedef struct {
int drag_orig_x, drag_orig_y;
int drag_orig_w, drag_orig_h;
bool resizing;
+
+ Client *pending_focus_client;
+ long pending_focus_time;
} DWNState;
extern DWNState *dwn;
diff --git a/include/workspace.h b/include/workspace.h
index 8960908..80f8cd4 100644
--- a/include/workspace.h
+++ b/include/workspace.h
@@ -50,4 +50,8 @@ void workspace_focus_next(void);
void workspace_focus_prev(void);
void workspace_focus_master(void);
+void workspace_mru_push(int workspace, Client *client);
+void workspace_mru_remove(int workspace, Client *client);
+Client *workspace_mru_get_previous(int workspace, Client *current);
+
#endif
diff --git a/site/configuration.html b/site/configuration.html
index 58daee2..b19b4f0 100644
--- a/site/configuration.html
+++ b/site/configuration.html
@@ -93,6 +93,11 @@
click |
click or follow (sloppy focus) |
+
+ focus_follow_delay |
+ 100 |
+ Delay in ms before focus switches in follow mode (0-1000) |
+
decorations |
true |
@@ -109,7 +114,8 @@
terminal = alacritty
launcher = rofi -show run
file_manager = nautilus
-focus_mode = click
+focus_mode = follow
+focus_follow_delay = 100
decorations = true
[appearance] - Visual Settings
@@ -432,6 +438,42 @@ path = ~/.config/dwn/autostart.d
~/.config/autostart/ - User XDG autostart entries
~/.config/dwn/autostart.d/ - DWN-specific symlinks and scripts
+ [demo] - Demo Mode
+
+ Configure demo mode timing. The demo showcases DWN features including live AI and search functionality.
+
+
+
+
+
+ | Option |
+ Description |
+
+
+
+
+ step_delay |
+ Time between demo steps in milliseconds (1000-30000, default: 4000) |
+
+
+ ai_timeout |
+ Timeout for AI/Exa API responses in milliseconds (5000-60000, default: 15000) |
+
+
+ window_timeout |
+ Timeout for window spawn operations in milliseconds (1000-30000, default: 5000) |
+
+
+
+
+
+ [demo]
+step_delay = 4000
+ai_timeout = 15000
+window_timeout = 5000
Complete Configuration Example
diff --git a/src/client.c b/src/client.c
index eeba94d..fde5c0d 100644
--- a/src/client.c
+++ b/src/client.c
@@ -11,6 +11,7 @@
#include "workspace.h"
#include "decorations.h"
#include "notifications.h"
+#include "layout.h"
#include
#include
@@ -43,6 +44,8 @@ Client *client_create(Window window)
client->workspace = dwn->current_workspace;
client->next = NULL;
client->prev = NULL;
+ client->mru_next = NULL;
+ client->mru_prev = NULL;
XWindowAttributes wa;
int orig_width = 640, orig_height = 480;
@@ -242,6 +245,9 @@ void client_unmanage(Client *client)
LOG_DEBUG("Unmanaging window: %lu", client->window);
+ client_sync_log("client_unmanage: remove from MRU");
+ workspace_mru_remove(client->workspace, client);
+
client_sync_log("client_unmanage: remove from workspace");
workspace_remove_client(client->workspace, client);
@@ -258,14 +264,22 @@ void client_unmanage(Client *client)
client_destroy(client);
client_sync_log("client_unmanage: focus next");
+ XGrabServer(dwn->display);
+
Workspace *ws = workspace_get_current();
if (ws != NULL && ws->focused == NULL) {
- Client *next = workspace_get_first_client(dwn->current_workspace);
+ Client *next = workspace_mru_get_previous(dwn->current_workspace, NULL);
if (next != NULL) {
client_focus(next);
+ } else {
+ XSetInputFocus(dwn->display, dwn->root, RevertToPointerRoot, CurrentTime);
+ atoms_set_active_window(None);
}
}
+ XUngrabServer(dwn->display);
+ XSync(dwn->display, False);
+
client_sync_log("client_unmanage: DONE");
}
@@ -336,6 +350,7 @@ void client_focus(Client *client)
if (ws != NULL) {
ws->focused = client;
+ workspace_mru_push(client->workspace, client);
}
client_sync_log("client_focus: raising");
@@ -672,6 +687,80 @@ void client_toggle_floating(Client *client)
client_set_floating(client, !(client->flags & CLIENT_FLOATING));
}
+bool client_is_maximized(Client *client)
+{
+ return client != NULL && (client->flags & CLIENT_MAXIMIZED);
+}
+
+void client_set_maximize(Client *client, bool maximized)
+{
+ if (client == NULL || dwn == NULL || dwn->display == NULL) {
+ return;
+ }
+
+ if (client->window == None) {
+ return;
+ }
+
+ XWindowAttributes wa;
+ if (!XGetWindowAttributes(dwn->display, client->window, &wa)) {
+ LOG_WARN("client_set_maximize: window %lu no longer exists", client->window);
+ return;
+ }
+
+ if (maximized) {
+ if (!(client->flags & CLIENT_MAXIMIZED)) {
+ client->old_x = client->x;
+ client->old_y = client->y;
+ client->old_width = client->width;
+ client->old_height = client->height;
+ }
+
+ client->flags |= CLIENT_MAXIMIZED;
+ client->flags |= CLIENT_FLOATING;
+
+ atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_MAXIMIZED_VERT, true);
+ atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_MAXIMIZED_HORZ, true);
+
+ int area_x, area_y, area_width, area_height;
+ layout_get_usable_area(&area_x, &area_y, &area_width, &area_height);
+
+ int gap = config_get_gap();
+ int title_height = config_get_title_height();
+ int border = client->border_width;
+
+ client->x = area_x + gap + border;
+ client->y = area_y + gap + title_height + border;
+ client->width = area_width - 2 * gap - 2 * border;
+ client->height = area_height - 2 * gap - title_height - 2 * border;
+
+ client_configure(client);
+ decorations_render(client, true);
+ client_raise(client);
+ } else {
+ client->flags &= ~CLIENT_MAXIMIZED;
+
+ atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_MAXIMIZED_VERT, false);
+ atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_MAXIMIZED_HORZ, false);
+
+ client->x = client->old_x;
+ client->y = client->old_y;
+ client->width = client->old_width;
+ client->height = client->old_height;
+
+ client_configure(client);
+ decorations_render(client, true);
+ }
+}
+
+void client_toggle_maximize(Client *client)
+{
+ if (client == NULL) {
+ return;
+ }
+ client_set_maximize(client, !(client->flags & CLIENT_MAXIMIZED));
+}
+
bool client_is_floating(Client *client)
{
diff --git a/src/config.c b/src/config.c
index 22cedb4..71b46a6 100644
--- a/src/config.c
+++ b/src/config.c
@@ -53,6 +53,7 @@ void config_set_defaults(Config *cfg)
strncpy(cfg->launcher, "dmenu_run", sizeof(cfg->launcher) - 1);
strncpy(cfg->file_manager, "thunar", sizeof(cfg->file_manager) - 1);
cfg->focus_mode = FOCUS_CLICK;
+ cfg->focus_follow_delay_ms = 100;
cfg->show_decorations = true;
cfg->border_width = DEFAULT_BORDER_WIDTH;
@@ -83,6 +84,10 @@ void config_set_defaults(Config *cfg)
strncpy(cfg->autostart_path, autostart_path, sizeof(cfg->autostart_path) - 1);
dwn_free(autostart_path);
}
+
+ cfg->demo_step_delay_ms = 4000;
+ cfg->demo_ai_timeout_ms = 15000;
+ cfg->demo_window_timeout_ms = 5000;
}
@@ -110,6 +115,9 @@ static void handle_config_entry(const char *section, const char *key,
} else {
cfg->focus_mode = FOCUS_CLICK;
}
+ } else if (strcmp(key, "focus_follow_delay") == 0) {
+ int val = atoi(value);
+ cfg->focus_follow_delay_ms = (val >= 0 && val <= 1000) ? val : 100;
} else if (strcmp(key, "decorations") == 0) {
cfg->show_decorations = (strcmp(value, "true") == 0 || strcmp(value, "1") == 0);
}
@@ -172,6 +180,17 @@ static void handle_config_entry(const char *section, const char *key,
dwn_free(expanded);
}
}
+ } else if (strcmp(section, "demo") == 0) {
+ if (strcmp(key, "step_delay") == 0) {
+ int val = atoi(value);
+ cfg->demo_step_delay_ms = (val >= 1000 && val <= 30000) ? val : 4000;
+ } else if (strcmp(key, "ai_timeout") == 0) {
+ int val = atoi(value);
+ cfg->demo_ai_timeout_ms = (val >= 5000 && val <= 60000) ? val : 15000;
+ } else if (strcmp(key, "window_timeout") == 0) {
+ int val = atoi(value);
+ cfg->demo_window_timeout_ms = (val >= 1000 && val <= 30000) ? val : 5000;
+ }
}
}
diff --git a/src/demo.c b/src/demo.c
index ff54213..d881ddd 100644
--- a/src/demo.c
+++ b/src/demo.c
@@ -19,8 +19,6 @@
#include
-#define DEMO_STEP_DELAY 3000
-
typedef struct {
bool active;
DemoPhase phase;
@@ -30,6 +28,18 @@ typedef struct {
uint32_t current_notification;
Window demo_window;
int demo_workspace_start;
+ LayoutType original_layout;
+
+ DemoWaitCondition wait_condition;
+ long wait_timeout;
+ long wait_started;
+
+ AIRequest *pending_ai_request;
+ ExaRequest *pending_exa_request;
+ bool ai_demo_completed;
+ bool exa_demo_completed;
+ char ai_response_buffer[1024];
+ char exa_response_buffer[1024];
} DemoState;
static DemoState demo = {0};
@@ -42,6 +52,239 @@ static void demo_notify(const char *title, const char *body, int timeout)
demo.current_notification = notification_show("DWN Demo", title, body, NULL, timeout);
}
+static int demo_get_step_delay(void)
+{
+ if (dwn != NULL && dwn->config != NULL) {
+ return dwn->config->demo_step_delay_ms;
+ }
+ return 4000;
+}
+
+static int demo_get_ai_timeout(void)
+{
+ if (dwn != NULL && dwn->config != NULL) {
+ return dwn->config->demo_ai_timeout_ms;
+ }
+ return 15000;
+}
+
+static void demo_wait_for(DemoWaitCondition condition, int timeout_ms)
+{
+ demo.wait_condition = condition;
+ demo.wait_timeout = timeout_ms;
+ demo.wait_started = get_time_ms();
+}
+
+static bool demo_check_wait_complete(void)
+{
+ if (demo.wait_condition == DEMO_WAIT_NONE) {
+ return true;
+ }
+
+ long elapsed = get_time_ms() - demo.wait_started;
+
+ switch (demo.wait_condition) {
+ case DEMO_WAIT_NONE:
+ return true;
+
+ case DEMO_WAIT_TIME:
+ if (elapsed >= demo.wait_timeout) {
+ demo.wait_condition = DEMO_WAIT_NONE;
+ return true;
+ }
+ return false;
+
+ case DEMO_WAIT_WINDOW_SPAWN:
+ if (demo.demo_window != None) {
+ Client *c = client_find_by_window(demo.demo_window);
+ if (c != NULL) {
+ demo.wait_condition = DEMO_WAIT_NONE;
+ return true;
+ }
+ }
+ if (elapsed >= demo.wait_timeout) {
+ demo.wait_condition = DEMO_WAIT_NONE;
+ return true;
+ }
+ return false;
+
+ case DEMO_WAIT_AI_RESPONSE:
+ if (demo.ai_demo_completed) {
+ demo.wait_condition = DEMO_WAIT_NONE;
+ return true;
+ }
+ if (elapsed >= demo.wait_timeout) {
+ demo_notify("AI Timeout", "AI request timed out. Continuing demo...", 2000);
+ demo.wait_condition = DEMO_WAIT_NONE;
+ if (demo.pending_ai_request != NULL) {
+ ai_cancel_request(demo.pending_ai_request);
+ demo.pending_ai_request = NULL;
+ }
+ return true;
+ }
+ return false;
+
+ case DEMO_WAIT_EXA_RESPONSE:
+ if (demo.exa_demo_completed) {
+ demo.wait_condition = DEMO_WAIT_NONE;
+ return true;
+ }
+ if (elapsed >= demo.wait_timeout) {
+ demo_notify("Search Timeout", "Exa search timed out. Continuing demo...", 2000);
+ demo.wait_condition = DEMO_WAIT_NONE;
+ demo.pending_exa_request = NULL;
+ return true;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+static void demo_ai_callback(AIRequest *req)
+{
+ if (req == NULL || !demo.active) {
+ return;
+ }
+
+ demo.pending_ai_request = NULL;
+ demo.ai_demo_completed = true;
+
+ if (req->state == AI_STATE_COMPLETED && req->response != NULL) {
+ size_t resp_len = strlen(req->response);
+ snprintf(demo.ai_response_buffer, sizeof(demo.ai_response_buffer),
+ "%s", req->response);
+
+ char display_buf[512];
+ snprintf(display_buf, sizeof(display_buf),
+ "AI Response:\n\n%.400s%s",
+ demo.ai_response_buffer,
+ resp_len > 400 ? "..." : "");
+ demo_notify("AI Query Complete", display_buf, 6000);
+ } else {
+ snprintf(demo.ai_response_buffer, sizeof(demo.ai_response_buffer),
+ "Error: Failed to get response");
+ demo_notify("AI Error", "Failed to get AI response", 3000);
+ }
+}
+
+static void demo_exa_callback(ExaRequest *req)
+{
+ if (req == NULL || !demo.active) {
+ return;
+ }
+
+ demo.pending_exa_request = NULL;
+ demo.exa_demo_completed = true;
+
+ if (req->state == AI_STATE_COMPLETED && req->result_count > 0) {
+ char results_buf[800];
+ size_t offset = 0;
+
+ offset += snprintf(results_buf + offset, sizeof(results_buf) - offset,
+ "Found %d results:\n\n", req->result_count);
+
+ for (int i = 0; i < req->result_count && i < 3; i++) {
+ offset += snprintf(results_buf + offset, sizeof(results_buf) - offset,
+ "%d. %s\n", i + 1, req->results[i].title);
+ if (offset >= sizeof(results_buf) - 50) break;
+ }
+
+ if (req->result_count > 3) {
+ snprintf(results_buf + offset, sizeof(results_buf) - offset,
+ "\n... and %d more", req->result_count - 3);
+ }
+
+ snprintf(demo.exa_response_buffer, sizeof(demo.exa_response_buffer),
+ "%s", results_buf);
+ demo_notify("Exa Search Complete", results_buf, 6000);
+ } else {
+ snprintf(demo.exa_response_buffer, sizeof(demo.exa_response_buffer),
+ "No results found");
+ demo_notify("Exa Search", "No results found for query", 3000);
+ }
+}
+
+static void demo_execute_snap_left(void)
+{
+ Workspace *ws = workspace_get_current();
+ if (ws != NULL && ws->focused != NULL) {
+ key_snap_left();
+ demo_notify("Snap Left", "Window snapped to left half (50% width)", 2000);
+ }
+}
+
+static void demo_execute_snap_right(void)
+{
+ Workspace *ws = workspace_get_current();
+ if (ws != NULL && ws->focused != NULL) {
+ key_snap_right();
+ demo_notify("Snap Right", "Window snapped to right half (50% width)", 2000);
+ }
+}
+
+static void demo_execute_ai_query(void)
+{
+ if (!ai_is_available()) {
+ demo.ai_demo_completed = true;
+ demo_notify("AI Unavailable",
+ "AI is not configured.\n\n"
+ "Set OPENROUTER_API_KEY to enable.", 3000);
+ return;
+ }
+
+ demo.ai_demo_completed = false;
+ demo.ai_response_buffer[0] = '\0';
+
+ ai_update_context();
+
+ Workspace *ws = workspace_get_current();
+ char prompt[512];
+
+ if (ws != NULL && ws->focused != NULL) {
+ snprintf(prompt, sizeof(prompt),
+ "Describe what I'm working on based on this context: "
+ "Current window: %s, Window class: %s. "
+ "Keep the response brief (2-3 sentences).",
+ ws->focused->title, ws->focused->class);
+ } else {
+ snprintf(prompt, sizeof(prompt),
+ "I'm using a Linux window manager called DWN. "
+ "Describe what a typical desktop workspace looks like. "
+ "Keep the response brief (2-3 sentences).");
+ }
+
+ demo_notify("AI Query", "Asking AI: \"Describe my current workspace\"\n\nWaiting for response...", 0);
+ demo.pending_ai_request = ai_send_request(prompt, demo_ai_callback);
+
+ if (demo.pending_ai_request == NULL) {
+ demo.ai_demo_completed = true;
+ demo_notify("AI Error", "Failed to send AI request", 3000);
+ }
+}
+
+static void demo_execute_exa_search(void)
+{
+ if (!exa_is_available()) {
+ demo.exa_demo_completed = true;
+ demo_notify("Exa Unavailable",
+ "Exa search is not configured.\n\n"
+ "Set EXA_API_KEY to enable semantic search.", 3000);
+ return;
+ }
+
+ demo.exa_demo_completed = false;
+ demo.exa_response_buffer[0] = '\0';
+
+ demo_notify("Exa Search", "Searching: \"What does molodetz mean?\"\n\nWaiting for results...", 0);
+ demo.pending_exa_request = exa_search("What does molodetz mean?", demo_exa_callback);
+
+ if (demo.pending_exa_request == NULL) {
+ demo.exa_demo_completed = true;
+ demo_notify("Exa Error", "Failed to send search request", 3000);
+ }
+}
+
static void demo_next_phase(void)
{
demo.phase++;
@@ -56,13 +299,12 @@ static void demo_next_step(void)
demo.step_start_time = get_time_ms();
}
-static long time_in_step(void)
-{
- return get_time_ms() - demo.step_start_time;
-}
-
static void demo_phase_intro(void)
{
+ if (!demo_check_wait_complete()) {
+ return;
+ }
+
switch (demo.step) {
case 0:
demo_notify("Welcome to DWN",
@@ -74,12 +316,11 @@ static void demo_phase_intro(void)
"- System tray and notifications\n\n"
"This demo will showcase all features.\n"
"Press Super+Shift+D to stop at any time.", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay() + 2000);
demo_next_step();
break;
case 1:
- if (time_in_step() > 5000) {
- demo_next_phase();
- }
+ demo_next_phase();
break;
}
}
@@ -89,6 +330,10 @@ static void demo_phase_window_mgmt(void)
Client *c;
Workspace *ws;
+ if (!demo_check_wait_complete()) {
+ return;
+ }
+
switch (demo.step) {
case 0:
demo_notify("Window Management",
@@ -99,65 +344,65 @@ static void demo_phase_window_mgmt(void)
"- Alt+F11: Fullscreen\n"
"- Alt+Tab: Cycle windows\n\n"
"Let's open a terminal...", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
demo_next_step();
break;
case 1:
- if (time_in_step() > 3000) {
- spawn_async("xterm -title 'Demo Terminal' -e 'echo DWN Demo Terminal; sleep 30' &");
- demo_next_step();
- }
+ spawn_async("xterm -title 'Demo Terminal' -e 'echo DWN Demo Terminal; sleep 60' &");
+ demo_wait_for(DEMO_WAIT_TIME, 2000);
+ demo_next_step();
break;
case 2:
- if (time_in_step() > 2000) {
- demo_notify("Window Created",
- "A terminal window was spawned.\n\n"
- "Windows can be moved by dragging the title bar\n"
- "or resized by dragging edges.\n\n"
- "Let's try minimizing...", 0);
- demo_next_step();
- }
+ demo_notify("Window Created",
+ "A terminal window was spawned.\n\n"
+ "Windows can be moved by dragging the title bar\n"
+ "or resized by dragging edges.\n\n"
+ "Let's try minimizing...", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
+ demo_next_step();
break;
case 3:
- if (time_in_step() > 3000) {
- ws = workspace_get_current();
- if (ws != NULL && ws->focused != NULL) {
- demo.demo_window = ws->focused->window;
- client_minimize(ws->focused);
- demo_notify("Window Minimized",
- "The window is now minimized.\n\n"
- "Look at the taskbar - minimized windows\n"
- "appear in [brackets] with a gray background.\n\n"
- "Click it or press Alt+F9 to restore.", 0);
- }
- demo_next_step();
+ ws = workspace_get_current();
+ if (ws != NULL && ws->focused != NULL) {
+ demo.demo_window = ws->focused->window;
+ client_minimize(ws->focused);
+ demo_notify("Window Minimized",
+ "The window is now minimized.\n\n"
+ "Look at the taskbar - minimized windows\n"
+ "appear in [brackets] with a gray background.\n\n"
+ "Click it or press Alt+F9 to restore.", 0);
}
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
+ demo_next_step();
break;
case 4:
- if (time_in_step() > 4000) {
- c = client_find_by_window(demo.demo_window);
- if (c != NULL) {
- client_restore(c);
- demo_notify("Window Restored",
- "The window is restored from the taskbar.\n\n"
- "This is how minimize/restore works in DWN.", 0);
- }
- demo_next_step();
+ c = client_find_by_window(demo.demo_window);
+ if (c != NULL) {
+ client_restore(c);
+ demo_notify("Window Restored",
+ "The window is restored from the taskbar.\n\n"
+ "This is how minimize/restore works in DWN.", 0);
}
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
+ demo_next_step();
break;
case 5:
- if (time_in_step() > 3000) {
- c = client_find_by_window(demo.demo_window);
- if (c != NULL) {
- client_close(c);
- }
- demo_next_phase();
+ c = client_find_by_window(demo.demo_window);
+ if (c != NULL) {
+ client_close(c);
}
+ demo.demo_window = None;
+ demo_next_phase();
break;
}
}
static void demo_phase_workspaces(void)
{
+ if (!demo_check_wait_complete()) {
+ return;
+ }
+
switch (demo.step) {
case 0:
demo.demo_workspace_start = dwn->current_workspace;
@@ -167,45 +412,40 @@ static void demo_phase_workspaces(void)
"- Shift+F1-F9: Move window to workspace\n"
"- Ctrl+Alt+Left/Right: Navigate\n\n"
"Watch the workspace indicators in the panel...", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
demo_next_step();
break;
case 1:
- if (time_in_step() > 3000) {
- workspace_switch(1);
- demo_notify("Workspace 2", "Switched to workspace 2", 1500);
- demo_next_step();
- }
+ workspace_switch(1);
+ demo_notify("Workspace 2", "Switched to workspace 2", 1500);
+ demo_wait_for(DEMO_WAIT_TIME, 2000);
+ demo_next_step();
break;
case 2:
- if (time_in_step() > 2000) {
- workspace_switch(2);
- demo_notify("Workspace 3", "Switched to workspace 3", 1500);
- demo_next_step();
- }
+ workspace_switch(2);
+ demo_notify("Workspace 3", "Switched to workspace 3", 1500);
+ demo_wait_for(DEMO_WAIT_TIME, 2000);
+ demo_next_step();
break;
case 3:
- if (time_in_step() > 2000) {
- workspace_switch(3);
- demo_notify("Workspace 4", "Switched to workspace 4", 1500);
- demo_next_step();
- }
+ workspace_switch(3);
+ demo_notify("Workspace 4", "Switched to workspace 4", 1500);
+ demo_wait_for(DEMO_WAIT_TIME, 2000);
+ demo_next_step();
break;
case 4:
- if (time_in_step() > 2000) {
- workspace_switch(demo.demo_workspace_start);
- demo_notify("Workspaces Complete",
- "Returned to the original workspace.\n\n"
- "Each workspace maintains its own:\n"
- "- Window layout and positions\n"
- "- Focused window\n"
- "- Layout mode (tiling/floating/monocle)", 0);
- demo_next_step();
- }
+ workspace_switch(demo.demo_workspace_start);
+ demo_notify("Workspaces Complete",
+ "Returned to the original workspace.\n\n"
+ "Each workspace maintains its own:\n"
+ "- Window layout and positions\n"
+ "- Focused window\n"
+ "- Layout mode (tiling/floating/monocle)", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
+ demo_next_step();
break;
case 5:
- if (time_in_step() > 4000) {
- demo_next_phase();
- }
+ demo_next_phase();
break;
}
}
@@ -214,6 +454,10 @@ static void demo_phase_layouts(void)
{
Workspace *ws = workspace_get_current();
+ if (!demo_check_wait_complete()) {
+ return;
+ }
+
switch (demo.step) {
case 0:
demo_notify("Layout Modes",
@@ -222,162 +466,268 @@ static void demo_phase_layouts(void)
"2. Floating: Free positioning like traditional WMs\n"
"3. Monocle: One window fullscreen at a time\n\n"
"Press Super+Space to cycle layouts.", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
demo_next_step();
break;
case 1:
- if (time_in_step() > 4000) {
- if (ws != NULL) {
- ws->layout = LAYOUT_TILING;
- workspace_arrange_current();
- }
- demo_notify("Tiling Layout",
- "In tiling mode, windows are automatically\n"
- "arranged in a main + stack pattern.\n\n"
- "- Super+H/L: Resize main area\n"
- "- Super+I/D: Add/remove from main\n\n"
- "No overlapping windows - efficient use of space.", 0);
- demo_next_step();
+ if (ws != NULL) {
+ ws->layout = LAYOUT_TILING;
+ workspace_arrange_current();
}
+ demo_notify("Tiling Layout",
+ "In tiling mode, windows are automatically\n"
+ "arranged in a main + stack pattern.\n\n"
+ "- Super+H/L: Resize main area\n"
+ "- Super+I/D: Add/remove from main\n\n"
+ "No overlapping windows - efficient use of space.", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
+ demo_next_step();
break;
case 2:
- if (time_in_step() > 4000) {
- if (ws != NULL) {
- ws->layout = LAYOUT_FLOATING;
- workspace_arrange_current();
- }
- demo_notify("Floating Layout",
- "In floating mode, windows can be freely\n"
- "positioned and resized anywhere.\n\n"
- "This is the traditional desktop behavior\n"
- "familiar from Windows and macOS.", 0);
- demo_next_step();
+ if (ws != NULL) {
+ ws->layout = LAYOUT_FLOATING;
+ workspace_arrange_current();
}
+ demo_notify("Floating Layout",
+ "In floating mode, windows can be freely\n"
+ "positioned and resized anywhere.\n\n"
+ "This is the traditional desktop behavior\n"
+ "familiar from Windows and macOS.", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
+ demo_next_step();
break;
case 3:
- if (time_in_step() > 4000) {
- if (ws != NULL) {
- ws->layout = LAYOUT_MONOCLE;
- workspace_arrange_current();
- }
- demo_notify("Monocle Layout",
- "In monocle mode, each window takes\n"
- "the full screen.\n\n"
- "Use Alt+Tab to switch between windows.\n"
- "Great for focused, single-task work.", 0);
- demo_next_step();
+ if (ws != NULL) {
+ ws->layout = LAYOUT_MONOCLE;
+ workspace_arrange_current();
}
+ demo_notify("Monocle Layout",
+ "In monocle mode, each window takes\n"
+ "the full screen.\n\n"
+ "Use Alt+Tab to switch between windows.\n"
+ "Great for focused, single-task work.", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
+ demo_next_step();
break;
case 4:
- if (time_in_step() > 4000) {
- if (ws != NULL) {
- ws->layout = LAYOUT_TILING;
- workspace_arrange_current();
- }
- demo_next_phase();
+ if (ws != NULL) {
+ ws->layout = demo.original_layout;
+ workspace_arrange_current();
}
+ demo_next_phase();
break;
}
}
static void demo_phase_snapping(void)
{
+ Workspace *ws;
+
+ if (!demo_check_wait_complete()) {
+ return;
+ }
+
switch (demo.step) {
case 0:
+ ws = workspace_get_current();
+ if (ws == NULL || ws->focused == NULL) {
+ demo_notify("Window Snapping",
+ "Snapping requires a window to demonstrate.\n\n"
+ "Spawning a demo terminal...", 0);
+ spawn_async("xterm -title 'Snap Demo' -e 'sleep 60' &");
+ demo_wait_for(DEMO_WAIT_TIME, 2000);
+ }
+ demo_next_step();
+ break;
+ case 1:
demo_notify("Window Snapping",
"Quickly arrange windows with snapping:\n\n"
"- Super+Left: Snap to left half (50%)\n"
"- Super+Right: Snap to right half (50%)\n\n"
- "Perfect for side-by-side comparisons\n"
- "or working with multiple documents.", 0);
+ "Watch the focused window snap to the left...", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
demo_next_step();
break;
- case 1:
- if (time_in_step() > 4000) {
- demo_next_phase();
- }
+ case 2:
+ demo_execute_snap_left();
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
+ demo_next_step();
+ break;
+ case 3:
+ demo_notify("Snap Right",
+ "Now snapping the window to the right half...", 0);
+ demo_wait_for(DEMO_WAIT_TIME, 1500);
+ demo_next_step();
+ break;
+ case 4:
+ demo_execute_snap_right();
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
+ demo_next_step();
+ break;
+ case 5:
+ demo_notify("Snapping Complete",
+ "Window snapping allows quick side-by-side layouts.\n\n"
+ "Perfect for comparing documents, coding with\n"
+ "documentation open, or multitasking.", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
+ demo_next_step();
+ break;
+ case 6:
+ demo_next_phase();
break;
}
}
static void demo_phase_panels(void)
{
+ if (!demo_check_wait_complete()) {
+ return;
+ }
+
switch (demo.step) {
case 0:
demo_notify("Panel System",
- "DWN has two panels with rich functionality:\n\n"
+ "DWN has two configurable panels:\n\n"
"TOP PANEL (left to right):\n"
"- Workspace indicators (click to switch)\n"
- "- Layout mode indicator\n"
- "- Taskbar (click to focus/restore)\n"
+ "- Layout mode indicator (T/F/M)\n"
+ "- Taskbar with window titles\n"
"- AI status indicator\n"
- "- System tray icons\n\n"
- "BOTTOM PANEL:\n"
- "- Clock with date\n"
- "- News ticker", 0);
+ "- System tray icons", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay() + 1000);
demo_next_step();
break;
case 1:
- if (time_in_step() > 5000) {
- demo_notify("System Tray",
- "The system tray shows:\n\n"
- "- Battery level (color-coded)\n"
- "- WiFi status (click for network list)\n"
- "- Volume (scroll to adjust, click to mute)\n"
- "- External app icons (Telegram, etc.)\n\n"
- "DWN implements the XEmbed protocol for\n"
- "full compatibility with tray applications.", 0);
- demo_next_step();
- }
+ demo_notify("Taskbar Features",
+ "The taskbar shows all windows on the workspace:\n\n"
+ "- Click a window title to focus it\n"
+ "- Minimized windows shown in [brackets]\n"
+ "- Focused window is highlighted\n"
+ "- Right-click for window menu", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
+ demo_next_step();
break;
case 2:
- if (time_in_step() > 5000) {
- demo_next_phase();
- }
+ demo_notify("System Tray",
+ "The system tray supports XEmbed protocol:\n\n"
+ "- Battery indicator (color-coded by level)\n"
+ "- WiFi status (click for network list)\n"
+ "- Volume control (scroll to adjust)\n"
+ "- External app icons (Telegram, nm-applet, etc.)\n\n"
+ "Applications can dock their status icons here.", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay() + 1000);
+ demo_next_step();
+ break;
+ case 3:
+ demo_notify("Bottom Panel",
+ "BOTTOM PANEL contains:\n\n"
+ "- Clock with current time and date\n"
+ "- News ticker with scrolling headlines\n\n"
+ "Headlines are color-coded by sentiment:\n"
+ " Green = Positive news\n"
+ " Red = Negative news\n"
+ " White = Neutral", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
+ demo_next_step();
+ break;
+ case 4:
+ demo_next_phase();
break;
}
}
static void demo_phase_ai(void)
{
+ if (!demo_check_wait_complete()) {
+ return;
+ }
+
switch (demo.step) {
case 0:
- if (dwn->ai_enabled) {
- demo_notify("AI Integration",
- "DWN includes AI-powered features:\n\n"
- "COMMAND PALETTE (Super+Shift+A):\n"
- "- Ask AI to launch apps: 'open firefox'\n"
- "- Get answers: 'what time is it'\n"
- "- Execute commands naturally\n\n"
- "CONTEXT ANALYSIS (Super+A):\n"
- "- See what you're working on\n"
- "- Get AI suggestions based on context\n\n"
- "EXA SEARCH (Super+Shift+E):\n"
- "- Semantic web search\n"
- "- Find relevant content by meaning", 0);
- } else {
- demo_notify("AI Integration",
- "DWN includes AI-powered features:\n\n"
- "AI is currently DISABLED.\n"
- "To enable, set these environment variables:\n\n"
- "OPENROUTER_API_KEY=sk-or-v1-your-key\n"
- "EXA_API_KEY=your-exa-key\n\n"
- "Then restart DWN to enable:\n"
- "- AI Command Palette (Super+Shift+A)\n"
- "- Context Analysis (Super+A)\n"
- "- Exa Semantic Search (Super+Shift+E)", 0);
- }
+ demo_notify("AI Integration",
+ "DWN includes AI-powered features:\n\n"
+ "COMMAND PALETTE (Super+Shift+A):\n"
+ "- Ask AI to launch apps: 'open firefox'\n"
+ "- Get answers: 'what time is it'\n"
+ "- Execute commands naturally\n\n"
+ "CONTEXT ANALYSIS (Super+A):\n"
+ "- See what you're working on\n"
+ "- Get AI suggestions based on context", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay() + 1000);
demo_next_step();
break;
case 1:
- if (time_in_step() > 6000) {
- demo_next_phase();
+ demo_notify("Live AI Demo",
+ "Let's try a live AI query...\n\n"
+ "Asking: \"Describe my current workspace\"", 0);
+ demo_wait_for(DEMO_WAIT_TIME, 2000);
+ demo_next_step();
+ break;
+ case 2:
+ demo_execute_ai_query();
+ if (ai_is_available()) {
+ demo_wait_for(DEMO_WAIT_AI_RESPONSE, demo_get_ai_timeout());
+ } else {
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
}
+ demo_next_step();
+ break;
+ case 3:
+ demo_notify("Exa Semantic Search",
+ "DWN also includes Exa semantic search:\n\n"
+ "EXA SEARCH (Super+Shift+E):\n"
+ "- Search the web by meaning, not just keywords\n"
+ "- Find relevant documentation and articles\n"
+ "- Results open in your default browser", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
+ demo_next_step();
+ break;
+ case 4:
+ demo_notify("Live Exa Demo",
+ "Let's try a live Exa search...\n\n"
+ "Searching: \"What does molodetz mean?\"", 0);
+ demo_wait_for(DEMO_WAIT_TIME, 2000);
+ demo_next_step();
+ break;
+ case 5:
+ demo_execute_exa_search();
+ if (exa_is_available()) {
+ demo_wait_for(DEMO_WAIT_EXA_RESPONSE, demo_get_ai_timeout());
+ } else {
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
+ }
+ demo_next_step();
+ break;
+ case 6:
+ if (ai_is_available() || exa_is_available()) {
+ demo_notify("AI Features Complete",
+ "AI integration provides intelligent assistance\n"
+ "directly within your window manager.\n\n"
+ "No need to switch to a browser or separate app\n"
+ "to get quick answers or find information.", 0);
+ } else {
+ demo_notify("AI Features",
+ "To enable AI features, configure API keys:\n\n"
+ "OPENROUTER_API_KEY=sk-or-v1-your-key\n"
+ "EXA_API_KEY=your-exa-key\n\n"
+ "Add to ~/.config/dwn/config or set as\n"
+ "environment variables, then restart DWN.", 0);
+ }
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
+ demo_next_step();
+ break;
+ case 7:
+ demo_next_phase();
break;
}
}
static void demo_phase_news(void)
{
+ if (!demo_check_wait_complete()) {
+ return;
+ }
+
switch (demo.step) {
case 0:
demo_notify("News Ticker",
@@ -391,39 +741,39 @@ static void demo_phase_news(void)
"- Super+Down: Next article\n"
"- Super+Up: Previous article\n"
"- Super+Return: Open in browser", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
demo_next_step();
break;
case 1:
- if (time_in_step() > 3000) {
- news_next_article();
- demo_notify("News Navigation", "Moved to next article", 1500);
- demo_next_step();
- }
+ news_next_article();
+ demo_notify("News Navigation", "Moved to next article", 1500);
+ demo_wait_for(DEMO_WAIT_TIME, 2000);
+ demo_next_step();
break;
case 2:
- if (time_in_step() > 2000) {
- news_next_article();
- demo_notify("News Navigation", "Moved to next article", 1500);
- demo_next_step();
- }
+ news_next_article();
+ demo_notify("News Navigation", "Moved to next article", 1500);
+ demo_wait_for(DEMO_WAIT_TIME, 2000);
+ demo_next_step();
break;
case 3:
- if (time_in_step() > 2000) {
- news_prev_article();
- demo_notify("News Navigation", "Moved to previous article", 1500);
- demo_next_step();
- }
+ news_prev_article();
+ demo_notify("News Navigation", "Moved to previous article", 1500);
+ demo_wait_for(DEMO_WAIT_TIME, 2000);
+ demo_next_step();
break;
case 4:
- if (time_in_step() > 2000) {
- demo_next_phase();
- }
+ demo_next_phase();
break;
}
}
static void demo_phase_shortcuts(void)
{
+ if (!demo_check_wait_complete()) {
+ return;
+ }
+
switch (demo.step) {
case 0:
demo_notify("Quick Reference",
@@ -443,18 +793,21 @@ static void demo_phase_shortcuts(void)
"Super+Left Snap left\n"
"Super+Right Snap right\n\n"
"Press Super+S anytime to see all shortcuts.", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay() * 2);
demo_next_step();
break;
case 1:
- if (time_in_step() > 8000) {
- demo_next_phase();
- }
+ demo_next_phase();
break;
}
}
static void demo_phase_complete(void)
{
+ if (!demo_check_wait_complete()) {
+ return;
+ }
+
switch (demo.step) {
case 0:
demo_notify("Demo Complete",
@@ -467,15 +820,14 @@ static void demo_phase_complete(void)
"Edit ~/.config/dwn/config to customize\n"
"colors, fonts, keybindings, and more.\n\n"
"Enjoy using DWN!", 0);
+ demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay() + 2000);
demo_next_step();
break;
case 1:
- if (time_in_step() > 6000) {
- notification_close(demo.current_notification);
- demo.current_notification = 0;
- demo.active = false;
- demo.phase = DEMO_IDLE;
- }
+ notification_close(demo.current_notification);
+ demo.current_notification = 0;
+ demo.active = false;
+ demo.phase = DEMO_IDLE;
break;
}
}
@@ -508,12 +860,28 @@ void demo_start(void)
return;
}
+ memset(&demo, 0, sizeof(demo));
+
demo.active = true;
demo.phase = DEMO_INTRO;
demo.step = 0;
demo.phase_start_time = get_time_ms();
demo.step_start_time = demo.phase_start_time;
demo.demo_workspace_start = dwn->current_workspace;
+ demo.demo_window = None;
+
+ Workspace *ws = workspace_get_current();
+ if (ws != NULL) {
+ demo.original_layout = ws->layout;
+ }
+
+ demo.wait_condition = DEMO_WAIT_NONE;
+ demo.pending_ai_request = NULL;
+ demo.pending_exa_request = NULL;
+ demo.ai_demo_completed = false;
+ demo.exa_demo_completed = false;
+ demo.ai_response_buffer[0] = '\0';
+ demo.exa_response_buffer[0] = '\0';
}
void demo_stop(void)
@@ -522,16 +890,30 @@ void demo_stop(void)
return;
}
+ if (demo.pending_ai_request != NULL) {
+ ai_cancel_request(demo.pending_ai_request);
+ demo.pending_ai_request = NULL;
+ }
+
+ demo.pending_exa_request = NULL;
+
if (demo.current_notification > 0) {
notification_close(demo.current_notification);
demo.current_notification = 0;
}
+ Workspace *ws = workspace_get_current();
+ if (ws != NULL && demo.original_layout != ws->layout) {
+ ws->layout = demo.original_layout;
+ workspace_arrange_current();
+ }
+
demo_notify("Demo Stopped", "Demo mode has been stopped.", 2000);
demo.active = false;
demo.phase = DEMO_IDLE;
demo.step = 0;
+ demo.wait_condition = DEMO_WAIT_NONE;
}
void demo_update(void)
diff --git a/src/keys.c b/src/keys.c
index c6acd77..6fcd568 100644
--- a/src/keys.c
+++ b/src/keys.c
@@ -608,7 +608,7 @@ void key_toggle_maximize(void)
{
Workspace *ws = workspace_get_current();
if (ws != NULL && ws->focused != NULL) {
- client_toggle_fullscreen(ws->focused);
+ client_toggle_maximize(ws->focused);
}
}
diff --git a/src/main.c b/src/main.c
index 49448a1..6d1ad0a 100644
--- a/src/main.c
+++ b/src/main.c
@@ -369,7 +369,29 @@ static void handle_enter_notify(XCrossingEvent *ev)
}
if (c != NULL && c->workspace == (unsigned int)dwn->current_workspace) {
- client_focus(c);
+ int delay = dwn->config->focus_follow_delay_ms;
+ if (delay <= 0) {
+ client_focus(c);
+ } else {
+ dwn->pending_focus_client = c;
+ dwn->pending_focus_time = get_time_ms() + delay;
+ }
+ }
+}
+
+static void handle_leave_notify(XCrossingEvent *ev)
+{
+ if (ev == NULL || dwn == NULL) {
+ return;
+ }
+
+ Client *c = client_find_by_frame(ev->window);
+ if (c == NULL) {
+ c = client_find_by_window(ev->window);
+ }
+
+ if (c != NULL && dwn->pending_focus_client == c) {
+ dwn->pending_focus_client = NULL;
}
}
@@ -568,6 +590,17 @@ static void handle_client_message(XClientMessageEvent *ev)
client_set_fullscreen(c, set);
}
}
+
+ if (prop1 == ewmh.NET_WM_STATE_MAXIMIZED_VERT ||
+ prop1 == ewmh.NET_WM_STATE_MAXIMIZED_HORZ ||
+ prop2 == ewmh.NET_WM_STATE_MAXIMIZED_VERT ||
+ prop2 == ewmh.NET_WM_STATE_MAXIMIZED_HORZ) {
+ if (toggle) {
+ client_toggle_maximize(c);
+ } else {
+ client_set_maximize(c, set);
+ }
+ }
}
} else if (ev->message_type == ewmh.NET_CURRENT_DESKTOP) {
int desktop = ev->data.l[0];
@@ -635,6 +668,9 @@ void dwn_handle_event(XEvent *ev)
case EnterNotify:
handle_enter_notify(&ev->xcrossing);
break;
+ case LeaveNotify:
+ handle_leave_notify(&ev->xcrossing);
+ break;
case ButtonPress:
handle_button_press(&ev->xbutton);
break;
@@ -840,6 +876,19 @@ void dwn_run(void)
notifications_update();
+ if (dwn->pending_focus_client != NULL) {
+ long now_focus = get_time_ms();
+ if (now_focus >= dwn->pending_focus_time) {
+ Workspace *ws = workspace_get_current();
+ if (ws != NULL && ws->focused != dwn->pending_focus_client) {
+ if (dwn->pending_focus_client->workspace == (unsigned int)dwn->current_workspace) {
+ client_focus(dwn->pending_focus_client);
+ }
+ }
+ dwn->pending_focus_client = NULL;
+ }
+ }
+
long now = get_time_ms();
if (now - last_news_update >= 16) {
diff --git a/src/panel.c b/src/panel.c
index f59c9a7..281ca7e 100644
--- a/src/panel.c
+++ b/src/panel.c
@@ -391,12 +391,11 @@ void panel_render_taskbar(Panel *panel, int x, int *width)
XFillRectangle(dpy, panel->buffer, dwn->gc,
current_x, 2, item_width - 2, panel->height - 4);
- char title[64];
+ char title[260];
if (minimized) {
snprintf(title, sizeof(title), "[%s]", c->title);
} else {
- strncpy(title, c->title, sizeof(title) - 1);
- title[sizeof(title) - 1] = '\0';
+ snprintf(title, sizeof(title), "%s", c->title);
}
int max_text_width = item_width - 8;
diff --git a/src/workspace.c b/src/workspace.c
index ac2aa70..67fcee9 100644
--- a/src/workspace.c
+++ b/src/workspace.c
@@ -29,6 +29,8 @@ void workspace_init(void)
ws->clients = NULL;
ws->focused = NULL;
+ ws->mru_head = NULL;
+ ws->mru_tail = NULL;
ws->layout = (dwn->config != NULL) ?
dwn->config->default_layout : LAYOUT_TILING;
ws->master_ratio = (dwn->config != NULL) ?
@@ -444,3 +446,75 @@ void workspace_focus_master(void)
client_focus(master);
}
}
+
+
+void workspace_mru_push(int workspace, Client *client)
+{
+ if (client == NULL || workspace < 0 || workspace >= MAX_WORKSPACES) {
+ return;
+ }
+
+ Workspace *ws = workspace_get(workspace);
+ if (ws == NULL) {
+ return;
+ }
+
+ workspace_mru_remove(workspace, client);
+
+ client->mru_next = ws->mru_head;
+ client->mru_prev = NULL;
+
+ if (ws->mru_head != NULL) {
+ ws->mru_head->mru_prev = client;
+ }
+ ws->mru_head = client;
+
+ if (ws->mru_tail == NULL) {
+ ws->mru_tail = client;
+ }
+}
+
+void workspace_mru_remove(int workspace, Client *client)
+{
+ if (client == NULL || workspace < 0 || workspace >= MAX_WORKSPACES) {
+ return;
+ }
+
+ Workspace *ws = workspace_get(workspace);
+ if (ws == NULL) {
+ return;
+ }
+
+ if (client->mru_prev != NULL) {
+ client->mru_prev->mru_next = client->mru_next;
+ } else if (ws->mru_head == client) {
+ ws->mru_head = client->mru_next;
+ }
+
+ if (client->mru_next != NULL) {
+ client->mru_next->mru_prev = client->mru_prev;
+ } else if (ws->mru_tail == client) {
+ ws->mru_tail = client->mru_prev;
+ }
+
+ client->mru_next = NULL;
+ client->mru_prev = NULL;
+}
+
+Client *workspace_mru_get_previous(int workspace, Client *current)
+{
+ Workspace *ws = workspace_get(workspace);
+ if (ws == NULL) {
+ return NULL;
+ }
+
+ Client *candidate = ws->mru_head;
+ while (candidate != NULL) {
+ if (candidate != current && !client_is_minimized(candidate)) {
+ return candidate;
+ }
+ candidate = candidate->mru_next;
+ }
+
+ return NULL;
+}