feat: add maximize window functionality and MRU-based focus management
feat: add focus follow delay configuration feat: add demo mode timing configurations and wait conditions docs: update configuration documentation for new options
This commit is contained in:
parent
d0b1669cb4
commit
395583aea9
BIN
build/ai.o
BIN
build/ai.o
Binary file not shown.
BIN
build/atoms.o
BIN
build/atoms.o
Binary file not shown.
Binary file not shown.
@ -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-server.h \
|
||||||
/usr/include/dbus-1.0/dbus/dbus-signature.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-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/client.h:
|
||||||
include/dwn.h:
|
include/dwn.h:
|
||||||
include/atoms.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-signature.h:
|
||||||
/usr/include/dbus-1.0/dbus/dbus-syntax.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:
|
||||||
|
|||||||
BIN
build/client.o
BIN
build/client.o
Binary file not shown.
BIN
build/config.o
BIN
build/config.o
Binary file not shown.
Binary file not shown.
BIN
build/demo.o
BIN
build/demo.o
Binary file not shown.
BIN
build/keys.o
BIN
build/keys.o
Binary file not shown.
BIN
build/layout.o
BIN
build/layout.o
Binary file not shown.
BIN
build/main.o
BIN
build/main.o
Binary file not shown.
BIN
build/news.o
BIN
build/news.o
Binary file not shown.
Binary file not shown.
BIN
build/panel.o
BIN
build/panel.o
Binary file not shown.
BIN
build/systray.o
BIN
build/systray.o
Binary file not shown.
BIN
build/util.o
BIN
build/util.o
Binary file not shown.
Binary file not shown.
@ -35,11 +35,14 @@ void client_update_title(Client *client);
|
|||||||
void client_update_class(Client *client);
|
void client_update_class(Client *client);
|
||||||
void client_set_fullscreen(Client *client, bool fullscreen);
|
void client_set_fullscreen(Client *client, bool fullscreen);
|
||||||
void client_toggle_fullscreen(Client *client);
|
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_set_floating(Client *client, bool floating);
|
||||||
void client_toggle_floating(Client *client);
|
void client_toggle_floating(Client *client);
|
||||||
|
|
||||||
bool client_is_floating(Client *client);
|
bool client_is_floating(Client *client);
|
||||||
bool client_is_fullscreen(Client *client);
|
bool client_is_fullscreen(Client *client);
|
||||||
|
bool client_is_maximized(Client *client);
|
||||||
bool client_is_minimized(Client *client);
|
bool client_is_minimized(Client *client);
|
||||||
bool client_is_dialog(Window window);
|
bool client_is_dialog(Window window);
|
||||||
bool client_is_dock(Window window);
|
bool client_is_dock(Window window);
|
||||||
|
|||||||
@ -31,6 +31,7 @@ struct Config {
|
|||||||
char launcher[128];
|
char launcher[128];
|
||||||
char file_manager[128];
|
char file_manager[128];
|
||||||
FocusMode focus_mode;
|
FocusMode focus_mode;
|
||||||
|
int focus_follow_delay_ms;
|
||||||
bool show_decorations;
|
bool show_decorations;
|
||||||
|
|
||||||
int border_width;
|
int border_width;
|
||||||
@ -58,6 +59,10 @@ struct Config {
|
|||||||
bool autostart_enabled;
|
bool autostart_enabled;
|
||||||
bool autostart_xdg;
|
bool autostart_xdg;
|
||||||
char autostart_path[512];
|
char autostart_path[512];
|
||||||
|
|
||||||
|
int demo_step_delay_ms;
|
||||||
|
int demo_ai_timeout_ms;
|
||||||
|
int demo_window_timeout_ms;
|
||||||
};
|
};
|
||||||
|
|
||||||
Config *config_create(void);
|
Config *config_create(void);
|
||||||
|
|||||||
@ -23,6 +23,14 @@ typedef enum {
|
|||||||
DEMO_COMPLETE
|
DEMO_COMPLETE
|
||||||
} DemoPhase;
|
} 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_init(void);
|
||||||
void demo_cleanup(void);
|
void demo_cleanup(void);
|
||||||
void demo_start(void);
|
void demo_start(void);
|
||||||
|
|||||||
@ -58,7 +58,8 @@ typedef enum {
|
|||||||
CLIENT_FULLSCREEN = (1 << 1),
|
CLIENT_FULLSCREEN = (1 << 1),
|
||||||
CLIENT_URGENT = (1 << 2),
|
CLIENT_URGENT = (1 << 2),
|
||||||
CLIENT_MINIMIZED = (1 << 3),
|
CLIENT_MINIMIZED = (1 << 3),
|
||||||
CLIENT_STICKY = (1 << 4)
|
CLIENT_STICKY = (1 << 4),
|
||||||
|
CLIENT_MAXIMIZED = (1 << 5)
|
||||||
} ClientFlags;
|
} ClientFlags;
|
||||||
|
|
||||||
typedef struct Client Client;
|
typedef struct Client Client;
|
||||||
@ -81,6 +82,8 @@ struct Client {
|
|||||||
char class[64];
|
char class[64];
|
||||||
Client *next;
|
Client *next;
|
||||||
Client *prev;
|
Client *prev;
|
||||||
|
Client *mru_next;
|
||||||
|
Client *mru_prev;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Monitor {
|
struct Monitor {
|
||||||
@ -93,6 +96,8 @@ struct Monitor {
|
|||||||
struct Workspace {
|
struct Workspace {
|
||||||
Client *clients;
|
Client *clients;
|
||||||
Client *focused;
|
Client *focused;
|
||||||
|
Client *mru_head;
|
||||||
|
Client *mru_tail;
|
||||||
LayoutType layout;
|
LayoutType layout;
|
||||||
float master_ratio;
|
float master_ratio;
|
||||||
int master_count;
|
int master_count;
|
||||||
@ -142,6 +147,9 @@ typedef struct {
|
|||||||
int drag_orig_x, drag_orig_y;
|
int drag_orig_x, drag_orig_y;
|
||||||
int drag_orig_w, drag_orig_h;
|
int drag_orig_w, drag_orig_h;
|
||||||
bool resizing;
|
bool resizing;
|
||||||
|
|
||||||
|
Client *pending_focus_client;
|
||||||
|
long pending_focus_time;
|
||||||
} DWNState;
|
} DWNState;
|
||||||
|
|
||||||
extern DWNState *dwn;
|
extern DWNState *dwn;
|
||||||
|
|||||||
@ -50,4 +50,8 @@ void workspace_focus_next(void);
|
|||||||
void workspace_focus_prev(void);
|
void workspace_focus_prev(void);
|
||||||
void workspace_focus_master(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
|
#endif
|
||||||
|
|||||||
@ -93,6 +93,11 @@
|
|||||||
<td><code>click</code></td>
|
<td><code>click</code></td>
|
||||||
<td><code>click</code> or <code>follow</code> (sloppy focus)</td>
|
<td><code>click</code> or <code>follow</code> (sloppy focus)</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>focus_follow_delay</code></td>
|
||||||
|
<td><code>100</code></td>
|
||||||
|
<td>Delay in ms before focus switches in follow mode (0-1000)</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>decorations</code></td>
|
<td><code>decorations</code></td>
|
||||||
<td><code>true</code></td>
|
<td><code>true</code></td>
|
||||||
@ -109,7 +114,8 @@
|
|||||||
terminal = alacritty
|
terminal = alacritty
|
||||||
launcher = rofi -show run
|
launcher = rofi -show run
|
||||||
file_manager = nautilus
|
file_manager = nautilus
|
||||||
focus_mode = click
|
focus_mode = follow
|
||||||
|
focus_follow_delay = 100
|
||||||
decorations = true</code></pre>
|
decorations = true</code></pre>
|
||||||
<h2 id="appearance" style="margin-top: 3rem;">[appearance] - Visual Settings</h2>
|
<h2 id="appearance" style="margin-top: 3rem;">[appearance] - Visual Settings</h2>
|
||||||
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
|
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
|
||||||
@ -432,6 +438,42 @@ path = ~/.config/dwn/autostart.d</code></pre>
|
|||||||
<li><code>~/.config/autostart/</code> - User XDG autostart entries</li>
|
<li><code>~/.config/autostart/</code> - User XDG autostart entries</li>
|
||||||
<li><code>~/.config/dwn/autostart.d/</code> - DWN-specific symlinks and scripts</li>
|
<li><code>~/.config/dwn/autostart.d/</code> - DWN-specific symlinks and scripts</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<h2 id="demo" style="margin-top: 3rem;">[demo] - Demo Mode</h2>
|
||||||
|
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
|
||||||
|
Configure demo mode timing. The demo showcases DWN features including live AI and search functionality.
|
||||||
|
</p>
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Option</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>step_delay</code></td>
|
||||||
|
<td>Time between demo steps in milliseconds (1000-30000, default: 4000)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>ai_timeout</code></td>
|
||||||
|
<td>Timeout for AI/Exa API responses in milliseconds (5000-60000, default: 15000)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>window_timeout</code></td>
|
||||||
|
<td>Timeout for window spawn operations in milliseconds (1000-30000, default: 5000)</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="code-header">
|
||||||
|
<span>Example</span>
|
||||||
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
||||||
|
</div>
|
||||||
|
<pre><code>[demo]
|
||||||
|
step_delay = 4000
|
||||||
|
ai_timeout = 15000
|
||||||
|
window_timeout = 5000</code></pre>
|
||||||
<h2 style="margin-top: 3rem;">Complete Configuration Example</h2>
|
<h2 style="margin-top: 3rem;">Complete Configuration Example</h2>
|
||||||
<div class="code-header">
|
<div class="code-header">
|
||||||
<span>~/.config/dwn/config</span>
|
<span>~/.config/dwn/config</span>
|
||||||
@ -444,6 +486,7 @@ terminal = alacritty
|
|||||||
launcher = rofi -show drun
|
launcher = rofi -show drun
|
||||||
file_manager = thunar
|
file_manager = thunar
|
||||||
focus_mode = click
|
focus_mode = click
|
||||||
|
focus_follow_delay = 100
|
||||||
decorations = true
|
decorations = true
|
||||||
[appearance]
|
[appearance]
|
||||||
border_width = 2
|
border_width = 2
|
||||||
@ -478,7 +521,11 @@ model = google/gemini-2.0-flash-exp:free
|
|||||||
[autostart]
|
[autostart]
|
||||||
enabled = true
|
enabled = true
|
||||||
xdg_autostart = true
|
xdg_autostart = true
|
||||||
path = ~/.config/dwn/autostart.d</code></pre>
|
path = ~/.config/dwn/autostart.d
|
||||||
|
[demo]
|
||||||
|
step_delay = 4000
|
||||||
|
ai_timeout = 15000
|
||||||
|
window_timeout = 5000</code></pre>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
91
src/client.c
91
src/client.c
@ -11,6 +11,7 @@
|
|||||||
#include "workspace.h"
|
#include "workspace.h"
|
||||||
#include "decorations.h"
|
#include "decorations.h"
|
||||||
#include "notifications.h"
|
#include "notifications.h"
|
||||||
|
#include "layout.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@ -43,6 +44,8 @@ Client *client_create(Window window)
|
|||||||
client->workspace = dwn->current_workspace;
|
client->workspace = dwn->current_workspace;
|
||||||
client->next = NULL;
|
client->next = NULL;
|
||||||
client->prev = NULL;
|
client->prev = NULL;
|
||||||
|
client->mru_next = NULL;
|
||||||
|
client->mru_prev = NULL;
|
||||||
|
|
||||||
XWindowAttributes wa;
|
XWindowAttributes wa;
|
||||||
int orig_width = 640, orig_height = 480;
|
int orig_width = 640, orig_height = 480;
|
||||||
@ -242,6 +245,9 @@ void client_unmanage(Client *client)
|
|||||||
|
|
||||||
LOG_DEBUG("Unmanaging window: %lu", client->window);
|
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");
|
client_sync_log("client_unmanage: remove from workspace");
|
||||||
workspace_remove_client(client->workspace, client);
|
workspace_remove_client(client->workspace, client);
|
||||||
|
|
||||||
@ -258,14 +264,22 @@ void client_unmanage(Client *client)
|
|||||||
client_destroy(client);
|
client_destroy(client);
|
||||||
|
|
||||||
client_sync_log("client_unmanage: focus next");
|
client_sync_log("client_unmanage: focus next");
|
||||||
|
XGrabServer(dwn->display);
|
||||||
|
|
||||||
Workspace *ws = workspace_get_current();
|
Workspace *ws = workspace_get_current();
|
||||||
if (ws != NULL && ws->focused == NULL) {
|
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) {
|
if (next != NULL) {
|
||||||
client_focus(next);
|
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");
|
client_sync_log("client_unmanage: DONE");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,6 +350,7 @@ void client_focus(Client *client)
|
|||||||
|
|
||||||
if (ws != NULL) {
|
if (ws != NULL) {
|
||||||
ws->focused = client;
|
ws->focused = client;
|
||||||
|
workspace_mru_push(client->workspace, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
client_sync_log("client_focus: raising");
|
client_sync_log("client_focus: raising");
|
||||||
@ -672,6 +687,80 @@ void client_toggle_floating(Client *client)
|
|||||||
client_set_floating(client, !(client->flags & CLIENT_FLOATING));
|
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)
|
bool client_is_floating(Client *client)
|
||||||
{
|
{
|
||||||
|
|||||||
19
src/config.c
19
src/config.c
@ -53,6 +53,7 @@ void config_set_defaults(Config *cfg)
|
|||||||
strncpy(cfg->launcher, "dmenu_run", sizeof(cfg->launcher) - 1);
|
strncpy(cfg->launcher, "dmenu_run", sizeof(cfg->launcher) - 1);
|
||||||
strncpy(cfg->file_manager, "thunar", sizeof(cfg->file_manager) - 1);
|
strncpy(cfg->file_manager, "thunar", sizeof(cfg->file_manager) - 1);
|
||||||
cfg->focus_mode = FOCUS_CLICK;
|
cfg->focus_mode = FOCUS_CLICK;
|
||||||
|
cfg->focus_follow_delay_ms = 100;
|
||||||
cfg->show_decorations = true;
|
cfg->show_decorations = true;
|
||||||
|
|
||||||
cfg->border_width = DEFAULT_BORDER_WIDTH;
|
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);
|
strncpy(cfg->autostart_path, autostart_path, sizeof(cfg->autostart_path) - 1);
|
||||||
dwn_free(autostart_path);
|
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 {
|
} else {
|
||||||
cfg->focus_mode = FOCUS_CLICK;
|
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) {
|
} else if (strcmp(key, "decorations") == 0) {
|
||||||
cfg->show_decorations = (strcmp(value, "true") == 0 || strcmp(value, "1") == 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);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
570
src/demo.c
570
src/demo.c
@ -19,8 +19,6 @@
|
|||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define DEMO_STEP_DELAY 3000
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool active;
|
bool active;
|
||||||
DemoPhase phase;
|
DemoPhase phase;
|
||||||
@ -30,6 +28,18 @@ typedef struct {
|
|||||||
uint32_t current_notification;
|
uint32_t current_notification;
|
||||||
Window demo_window;
|
Window demo_window;
|
||||||
int demo_workspace_start;
|
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;
|
} DemoState;
|
||||||
|
|
||||||
static DemoState demo = {0};
|
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);
|
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)
|
static void demo_next_phase(void)
|
||||||
{
|
{
|
||||||
demo.phase++;
|
demo.phase++;
|
||||||
@ -56,13 +299,12 @@ static void demo_next_step(void)
|
|||||||
demo.step_start_time = get_time_ms();
|
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)
|
static void demo_phase_intro(void)
|
||||||
{
|
{
|
||||||
|
if (!demo_check_wait_complete()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (demo.step) {
|
switch (demo.step) {
|
||||||
case 0:
|
case 0:
|
||||||
demo_notify("Welcome to DWN",
|
demo_notify("Welcome to DWN",
|
||||||
@ -74,12 +316,11 @@ static void demo_phase_intro(void)
|
|||||||
"- System tray and notifications\n\n"
|
"- System tray and notifications\n\n"
|
||||||
"This demo will showcase all features.\n"
|
"This demo will showcase all features.\n"
|
||||||
"Press Super+Shift+D to stop at any time.", 0);
|
"Press Super+Shift+D to stop at any time.", 0);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay() + 2000);
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
if (time_in_step() > 5000) {
|
|
||||||
demo_next_phase();
|
demo_next_phase();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,6 +330,10 @@ static void demo_phase_window_mgmt(void)
|
|||||||
Client *c;
|
Client *c;
|
||||||
Workspace *ws;
|
Workspace *ws;
|
||||||
|
|
||||||
|
if (!demo_check_wait_complete()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (demo.step) {
|
switch (demo.step) {
|
||||||
case 0:
|
case 0:
|
||||||
demo_notify("Window Management",
|
demo_notify("Window Management",
|
||||||
@ -99,26 +344,24 @@ static void demo_phase_window_mgmt(void)
|
|||||||
"- Alt+F11: Fullscreen\n"
|
"- Alt+F11: Fullscreen\n"
|
||||||
"- Alt+Tab: Cycle windows\n\n"
|
"- Alt+Tab: Cycle windows\n\n"
|
||||||
"Let's open a terminal...", 0);
|
"Let's open a terminal...", 0);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
if (time_in_step() > 3000) {
|
spawn_async("xterm -title 'Demo Terminal' -e 'echo DWN Demo Terminal; sleep 60' &");
|
||||||
spawn_async("xterm -title 'Demo Terminal' -e 'echo DWN Demo Terminal; sleep 30' &");
|
demo_wait_for(DEMO_WAIT_TIME, 2000);
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
if (time_in_step() > 2000) {
|
|
||||||
demo_notify("Window Created",
|
demo_notify("Window Created",
|
||||||
"A terminal window was spawned.\n\n"
|
"A terminal window was spawned.\n\n"
|
||||||
"Windows can be moved by dragging the title bar\n"
|
"Windows can be moved by dragging the title bar\n"
|
||||||
"or resized by dragging edges.\n\n"
|
"or resized by dragging edges.\n\n"
|
||||||
"Let's try minimizing...", 0);
|
"Let's try minimizing...", 0);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
if (time_in_step() > 3000) {
|
|
||||||
ws = workspace_get_current();
|
ws = workspace_get_current();
|
||||||
if (ws != NULL && ws->focused != NULL) {
|
if (ws != NULL && ws->focused != NULL) {
|
||||||
demo.demo_window = ws->focused->window;
|
demo.demo_window = ws->focused->window;
|
||||||
@ -129,11 +372,10 @@ static void demo_phase_window_mgmt(void)
|
|||||||
"appear in [brackets] with a gray background.\n\n"
|
"appear in [brackets] with a gray background.\n\n"
|
||||||
"Click it or press Alt+F9 to restore.", 0);
|
"Click it or press Alt+F9 to restore.", 0);
|
||||||
}
|
}
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
if (time_in_step() > 4000) {
|
|
||||||
c = client_find_by_window(demo.demo_window);
|
c = client_find_by_window(demo.demo_window);
|
||||||
if (c != NULL) {
|
if (c != NULL) {
|
||||||
client_restore(c);
|
client_restore(c);
|
||||||
@ -141,23 +383,26 @@ static void demo_phase_window_mgmt(void)
|
|||||||
"The window is restored from the taskbar.\n\n"
|
"The window is restored from the taskbar.\n\n"
|
||||||
"This is how minimize/restore works in DWN.", 0);
|
"This is how minimize/restore works in DWN.", 0);
|
||||||
}
|
}
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
if (time_in_step() > 3000) {
|
|
||||||
c = client_find_by_window(demo.demo_window);
|
c = client_find_by_window(demo.demo_window);
|
||||||
if (c != NULL) {
|
if (c != NULL) {
|
||||||
client_close(c);
|
client_close(c);
|
||||||
}
|
}
|
||||||
|
demo.demo_window = None;
|
||||||
demo_next_phase();
|
demo_next_phase();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void demo_phase_workspaces(void)
|
static void demo_phase_workspaces(void)
|
||||||
{
|
{
|
||||||
|
if (!demo_check_wait_complete()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (demo.step) {
|
switch (demo.step) {
|
||||||
case 0:
|
case 0:
|
||||||
demo.demo_workspace_start = dwn->current_workspace;
|
demo.demo_workspace_start = dwn->current_workspace;
|
||||||
@ -167,31 +412,28 @@ static void demo_phase_workspaces(void)
|
|||||||
"- Shift+F1-F9: Move window to workspace\n"
|
"- Shift+F1-F9: Move window to workspace\n"
|
||||||
"- Ctrl+Alt+Left/Right: Navigate\n\n"
|
"- Ctrl+Alt+Left/Right: Navigate\n\n"
|
||||||
"Watch the workspace indicators in the panel...", 0);
|
"Watch the workspace indicators in the panel...", 0);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
if (time_in_step() > 3000) {
|
|
||||||
workspace_switch(1);
|
workspace_switch(1);
|
||||||
demo_notify("Workspace 2", "Switched to workspace 2", 1500);
|
demo_notify("Workspace 2", "Switched to workspace 2", 1500);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, 2000);
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
if (time_in_step() > 2000) {
|
|
||||||
workspace_switch(2);
|
workspace_switch(2);
|
||||||
demo_notify("Workspace 3", "Switched to workspace 3", 1500);
|
demo_notify("Workspace 3", "Switched to workspace 3", 1500);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, 2000);
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
if (time_in_step() > 2000) {
|
|
||||||
workspace_switch(3);
|
workspace_switch(3);
|
||||||
demo_notify("Workspace 4", "Switched to workspace 4", 1500);
|
demo_notify("Workspace 4", "Switched to workspace 4", 1500);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, 2000);
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
if (time_in_step() > 2000) {
|
|
||||||
workspace_switch(demo.demo_workspace_start);
|
workspace_switch(demo.demo_workspace_start);
|
||||||
demo_notify("Workspaces Complete",
|
demo_notify("Workspaces Complete",
|
||||||
"Returned to the original workspace.\n\n"
|
"Returned to the original workspace.\n\n"
|
||||||
@ -199,13 +441,11 @@ static void demo_phase_workspaces(void)
|
|||||||
"- Window layout and positions\n"
|
"- Window layout and positions\n"
|
||||||
"- Focused window\n"
|
"- Focused window\n"
|
||||||
"- Layout mode (tiling/floating/monocle)", 0);
|
"- Layout mode (tiling/floating/monocle)", 0);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
if (time_in_step() > 4000) {
|
|
||||||
demo_next_phase();
|
demo_next_phase();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,6 +454,10 @@ static void demo_phase_layouts(void)
|
|||||||
{
|
{
|
||||||
Workspace *ws = workspace_get_current();
|
Workspace *ws = workspace_get_current();
|
||||||
|
|
||||||
|
if (!demo_check_wait_complete()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (demo.step) {
|
switch (demo.step) {
|
||||||
case 0:
|
case 0:
|
||||||
demo_notify("Layout Modes",
|
demo_notify("Layout Modes",
|
||||||
@ -222,10 +466,10 @@ static void demo_phase_layouts(void)
|
|||||||
"2. Floating: Free positioning like traditional WMs\n"
|
"2. Floating: Free positioning like traditional WMs\n"
|
||||||
"3. Monocle: One window fullscreen at a time\n\n"
|
"3. Monocle: One window fullscreen at a time\n\n"
|
||||||
"Press Super+Space to cycle layouts.", 0);
|
"Press Super+Space to cycle layouts.", 0);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
if (time_in_step() > 4000) {
|
|
||||||
if (ws != NULL) {
|
if (ws != NULL) {
|
||||||
ws->layout = LAYOUT_TILING;
|
ws->layout = LAYOUT_TILING;
|
||||||
workspace_arrange_current();
|
workspace_arrange_current();
|
||||||
@ -236,11 +480,10 @@ static void demo_phase_layouts(void)
|
|||||||
"- Super+H/L: Resize main area\n"
|
"- Super+H/L: Resize main area\n"
|
||||||
"- Super+I/D: Add/remove from main\n\n"
|
"- Super+I/D: Add/remove from main\n\n"
|
||||||
"No overlapping windows - efficient use of space.", 0);
|
"No overlapping windows - efficient use of space.", 0);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
if (time_in_step() > 4000) {
|
|
||||||
if (ws != NULL) {
|
if (ws != NULL) {
|
||||||
ws->layout = LAYOUT_FLOATING;
|
ws->layout = LAYOUT_FLOATING;
|
||||||
workspace_arrange_current();
|
workspace_arrange_current();
|
||||||
@ -250,11 +493,10 @@ static void demo_phase_layouts(void)
|
|||||||
"positioned and resized anywhere.\n\n"
|
"positioned and resized anywhere.\n\n"
|
||||||
"This is the traditional desktop behavior\n"
|
"This is the traditional desktop behavior\n"
|
||||||
"familiar from Windows and macOS.", 0);
|
"familiar from Windows and macOS.", 0);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
if (time_in_step() > 4000) {
|
|
||||||
if (ws != NULL) {
|
if (ws != NULL) {
|
||||||
ws->layout = LAYOUT_MONOCLE;
|
ws->layout = LAYOUT_MONOCLE;
|
||||||
workspace_arrange_current();
|
workspace_arrange_current();
|
||||||
@ -264,84 +506,144 @@ static void demo_phase_layouts(void)
|
|||||||
"the full screen.\n\n"
|
"the full screen.\n\n"
|
||||||
"Use Alt+Tab to switch between windows.\n"
|
"Use Alt+Tab to switch between windows.\n"
|
||||||
"Great for focused, single-task work.", 0);
|
"Great for focused, single-task work.", 0);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
if (time_in_step() > 4000) {
|
|
||||||
if (ws != NULL) {
|
if (ws != NULL) {
|
||||||
ws->layout = LAYOUT_TILING;
|
ws->layout = demo.original_layout;
|
||||||
workspace_arrange_current();
|
workspace_arrange_current();
|
||||||
}
|
}
|
||||||
demo_next_phase();
|
demo_next_phase();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void demo_phase_snapping(void)
|
static void demo_phase_snapping(void)
|
||||||
{
|
{
|
||||||
|
Workspace *ws;
|
||||||
|
|
||||||
|
if (!demo_check_wait_complete()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (demo.step) {
|
switch (demo.step) {
|
||||||
case 0:
|
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",
|
demo_notify("Window Snapping",
|
||||||
"Quickly arrange windows with snapping:\n\n"
|
"Quickly arrange windows with snapping:\n\n"
|
||||||
"- Super+Left: Snap to left half (50%)\n"
|
"- Super+Left: Snap to left half (50%)\n"
|
||||||
"- Super+Right: Snap to right half (50%)\n\n"
|
"- Super+Right: Snap to right half (50%)\n\n"
|
||||||
"Perfect for side-by-side comparisons\n"
|
"Watch the focused window snap to the left...", 0);
|
||||||
"or working with multiple documents.", 0);
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 2:
|
||||||
if (time_in_step() > 4000) {
|
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();
|
demo_next_phase();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void demo_phase_panels(void)
|
static void demo_phase_panels(void)
|
||||||
{
|
{
|
||||||
|
if (!demo_check_wait_complete()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (demo.step) {
|
switch (demo.step) {
|
||||||
case 0:
|
case 0:
|
||||||
demo_notify("Panel System",
|
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"
|
"TOP PANEL (left to right):\n"
|
||||||
"- Workspace indicators (click to switch)\n"
|
"- Workspace indicators (click to switch)\n"
|
||||||
"- Layout mode indicator\n"
|
"- Layout mode indicator (T/F/M)\n"
|
||||||
"- Taskbar (click to focus/restore)\n"
|
"- Taskbar with window titles\n"
|
||||||
"- AI status indicator\n"
|
"- AI status indicator\n"
|
||||||
"- System tray icons\n\n"
|
"- System tray icons", 0);
|
||||||
"BOTTOM PANEL:\n"
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay() + 1000);
|
||||||
"- Clock with date\n"
|
|
||||||
"- News ticker", 0);
|
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
if (time_in_step() > 5000) {
|
demo_notify("Taskbar Features",
|
||||||
demo_notify("System Tray",
|
"The taskbar shows all windows on the workspace:\n\n"
|
||||||
"The system tray shows:\n\n"
|
"- Click a window title to focus it\n"
|
||||||
"- Battery level (color-coded)\n"
|
"- Minimized windows shown in [brackets]\n"
|
||||||
"- WiFi status (click for network list)\n"
|
"- Focused window is highlighted\n"
|
||||||
"- Volume (scroll to adjust, click to mute)\n"
|
"- Right-click for window menu", 0);
|
||||||
"- External app icons (Telegram, etc.)\n\n"
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
|
||||||
"DWN implements the XEmbed protocol for\n"
|
|
||||||
"full compatibility with tray applications.", 0);
|
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
if (time_in_step() > 5000) {
|
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();
|
demo_next_phase();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void demo_phase_ai(void)
|
static void demo_phase_ai(void)
|
||||||
{
|
{
|
||||||
|
if (!demo_check_wait_complete()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (demo.step) {
|
switch (demo.step) {
|
||||||
case 0:
|
case 0:
|
||||||
if (dwn->ai_enabled) {
|
|
||||||
demo_notify("AI Integration",
|
demo_notify("AI Integration",
|
||||||
"DWN includes AI-powered features:\n\n"
|
"DWN includes AI-powered features:\n\n"
|
||||||
"COMMAND PALETTE (Super+Shift+A):\n"
|
"COMMAND PALETTE (Super+Shift+A):\n"
|
||||||
@ -350,34 +652,82 @@ static void demo_phase_ai(void)
|
|||||||
"- Execute commands naturally\n\n"
|
"- Execute commands naturally\n\n"
|
||||||
"CONTEXT ANALYSIS (Super+A):\n"
|
"CONTEXT ANALYSIS (Super+A):\n"
|
||||||
"- See what you're working on\n"
|
"- See what you're working on\n"
|
||||||
"- Get AI suggestions based on context\n\n"
|
"- Get AI suggestions based on context", 0);
|
||||||
"EXA SEARCH (Super+Shift+E):\n"
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay() + 1000);
|
||||||
"- 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_next_step();
|
demo_next_step();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
if (time_in_step() > 6000) {
|
demo_notify("Live AI Demo",
|
||||||
demo_next_phase();
|
"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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void demo_phase_news(void)
|
static void demo_phase_news(void)
|
||||||
{
|
{
|
||||||
|
if (!demo_check_wait_complete()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (demo.step) {
|
switch (demo.step) {
|
||||||
case 0:
|
case 0:
|
||||||
demo_notify("News Ticker",
|
demo_notify("News Ticker",
|
||||||
@ -391,39 +741,39 @@ static void demo_phase_news(void)
|
|||||||
"- Super+Down: Next article\n"
|
"- Super+Down: Next article\n"
|
||||||
"- Super+Up: Previous article\n"
|
"- Super+Up: Previous article\n"
|
||||||
"- Super+Return: Open in browser", 0);
|
"- Super+Return: Open in browser", 0);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay());
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
if (time_in_step() > 3000) {
|
|
||||||
news_next_article();
|
news_next_article();
|
||||||
demo_notify("News Navigation", "Moved to next article", 1500);
|
demo_notify("News Navigation", "Moved to next article", 1500);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, 2000);
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
if (time_in_step() > 2000) {
|
|
||||||
news_next_article();
|
news_next_article();
|
||||||
demo_notify("News Navigation", "Moved to next article", 1500);
|
demo_notify("News Navigation", "Moved to next article", 1500);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, 2000);
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
if (time_in_step() > 2000) {
|
|
||||||
news_prev_article();
|
news_prev_article();
|
||||||
demo_notify("News Navigation", "Moved to previous article", 1500);
|
demo_notify("News Navigation", "Moved to previous article", 1500);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, 2000);
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
if (time_in_step() > 2000) {
|
|
||||||
demo_next_phase();
|
demo_next_phase();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void demo_phase_shortcuts(void)
|
static void demo_phase_shortcuts(void)
|
||||||
{
|
{
|
||||||
|
if (!demo_check_wait_complete()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (demo.step) {
|
switch (demo.step) {
|
||||||
case 0:
|
case 0:
|
||||||
demo_notify("Quick Reference",
|
demo_notify("Quick Reference",
|
||||||
@ -443,18 +793,21 @@ static void demo_phase_shortcuts(void)
|
|||||||
"Super+Left Snap left\n"
|
"Super+Left Snap left\n"
|
||||||
"Super+Right Snap right\n\n"
|
"Super+Right Snap right\n\n"
|
||||||
"Press Super+S anytime to see all shortcuts.", 0);
|
"Press Super+S anytime to see all shortcuts.", 0);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay() * 2);
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
if (time_in_step() > 8000) {
|
|
||||||
demo_next_phase();
|
demo_next_phase();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void demo_phase_complete(void)
|
static void demo_phase_complete(void)
|
||||||
{
|
{
|
||||||
|
if (!demo_check_wait_complete()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (demo.step) {
|
switch (demo.step) {
|
||||||
case 0:
|
case 0:
|
||||||
demo_notify("Demo Complete",
|
demo_notify("Demo Complete",
|
||||||
@ -467,15 +820,14 @@ static void demo_phase_complete(void)
|
|||||||
"Edit ~/.config/dwn/config to customize\n"
|
"Edit ~/.config/dwn/config to customize\n"
|
||||||
"colors, fonts, keybindings, and more.\n\n"
|
"colors, fonts, keybindings, and more.\n\n"
|
||||||
"Enjoy using DWN!", 0);
|
"Enjoy using DWN!", 0);
|
||||||
|
demo_wait_for(DEMO_WAIT_TIME, demo_get_step_delay() + 2000);
|
||||||
demo_next_step();
|
demo_next_step();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
if (time_in_step() > 6000) {
|
|
||||||
notification_close(demo.current_notification);
|
notification_close(demo.current_notification);
|
||||||
demo.current_notification = 0;
|
demo.current_notification = 0;
|
||||||
demo.active = false;
|
demo.active = false;
|
||||||
demo.phase = DEMO_IDLE;
|
demo.phase = DEMO_IDLE;
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -508,12 +860,28 @@ void demo_start(void)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
memset(&demo, 0, sizeof(demo));
|
||||||
|
|
||||||
demo.active = true;
|
demo.active = true;
|
||||||
demo.phase = DEMO_INTRO;
|
demo.phase = DEMO_INTRO;
|
||||||
demo.step = 0;
|
demo.step = 0;
|
||||||
demo.phase_start_time = get_time_ms();
|
demo.phase_start_time = get_time_ms();
|
||||||
demo.step_start_time = demo.phase_start_time;
|
demo.step_start_time = demo.phase_start_time;
|
||||||
demo.demo_workspace_start = dwn->current_workspace;
|
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)
|
void demo_stop(void)
|
||||||
@ -522,16 +890,30 @@ void demo_stop(void)
|
|||||||
return;
|
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) {
|
if (demo.current_notification > 0) {
|
||||||
notification_close(demo.current_notification);
|
notification_close(demo.current_notification);
|
||||||
demo.current_notification = 0;
|
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_notify("Demo Stopped", "Demo mode has been stopped.", 2000);
|
||||||
|
|
||||||
demo.active = false;
|
demo.active = false;
|
||||||
demo.phase = DEMO_IDLE;
|
demo.phase = DEMO_IDLE;
|
||||||
demo.step = 0;
|
demo.step = 0;
|
||||||
|
demo.wait_condition = DEMO_WAIT_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void demo_update(void)
|
void demo_update(void)
|
||||||
|
|||||||
@ -608,7 +608,7 @@ void key_toggle_maximize(void)
|
|||||||
{
|
{
|
||||||
Workspace *ws = workspace_get_current();
|
Workspace *ws = workspace_get_current();
|
||||||
if (ws != NULL && ws->focused != NULL) {
|
if (ws != NULL && ws->focused != NULL) {
|
||||||
client_toggle_fullscreen(ws->focused);
|
client_toggle_maximize(ws->focused);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
49
src/main.c
49
src/main.c
@ -369,7 +369,29 @@ static void handle_enter_notify(XCrossingEvent *ev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (c != NULL && c->workspace == (unsigned int)dwn->current_workspace) {
|
if (c != NULL && c->workspace == (unsigned int)dwn->current_workspace) {
|
||||||
|
int delay = dwn->config->focus_follow_delay_ms;
|
||||||
|
if (delay <= 0) {
|
||||||
client_focus(c);
|
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);
|
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) {
|
} else if (ev->message_type == ewmh.NET_CURRENT_DESKTOP) {
|
||||||
int desktop = ev->data.l[0];
|
int desktop = ev->data.l[0];
|
||||||
@ -635,6 +668,9 @@ void dwn_handle_event(XEvent *ev)
|
|||||||
case EnterNotify:
|
case EnterNotify:
|
||||||
handle_enter_notify(&ev->xcrossing);
|
handle_enter_notify(&ev->xcrossing);
|
||||||
break;
|
break;
|
||||||
|
case LeaveNotify:
|
||||||
|
handle_leave_notify(&ev->xcrossing);
|
||||||
|
break;
|
||||||
case ButtonPress:
|
case ButtonPress:
|
||||||
handle_button_press(&ev->xbutton);
|
handle_button_press(&ev->xbutton);
|
||||||
break;
|
break;
|
||||||
@ -840,6 +876,19 @@ void dwn_run(void)
|
|||||||
|
|
||||||
notifications_update();
|
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();
|
long now = get_time_ms();
|
||||||
|
|
||||||
if (now - last_news_update >= 16) {
|
if (now - last_news_update >= 16) {
|
||||||
|
|||||||
@ -391,12 +391,11 @@ void panel_render_taskbar(Panel *panel, int x, int *width)
|
|||||||
XFillRectangle(dpy, panel->buffer, dwn->gc,
|
XFillRectangle(dpy, panel->buffer, dwn->gc,
|
||||||
current_x, 2, item_width - 2, panel->height - 4);
|
current_x, 2, item_width - 2, panel->height - 4);
|
||||||
|
|
||||||
char title[64];
|
char title[260];
|
||||||
if (minimized) {
|
if (minimized) {
|
||||||
snprintf(title, sizeof(title), "[%s]", c->title);
|
snprintf(title, sizeof(title), "[%s]", c->title);
|
||||||
} else {
|
} else {
|
||||||
strncpy(title, c->title, sizeof(title) - 1);
|
snprintf(title, sizeof(title), "%s", c->title);
|
||||||
title[sizeof(title) - 1] = '\0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int max_text_width = item_width - 8;
|
int max_text_width = item_width - 8;
|
||||||
|
|||||||
@ -29,6 +29,8 @@ void workspace_init(void)
|
|||||||
|
|
||||||
ws->clients = NULL;
|
ws->clients = NULL;
|
||||||
ws->focused = NULL;
|
ws->focused = NULL;
|
||||||
|
ws->mru_head = NULL;
|
||||||
|
ws->mru_tail = NULL;
|
||||||
ws->layout = (dwn->config != NULL) ?
|
ws->layout = (dwn->config != NULL) ?
|
||||||
dwn->config->default_layout : LAYOUT_TILING;
|
dwn->config->default_layout : LAYOUT_TILING;
|
||||||
ws->master_ratio = (dwn->config != NULL) ?
|
ws->master_ratio = (dwn->config != NULL) ?
|
||||||
@ -444,3 +446,75 @@ void workspace_focus_master(void)
|
|||||||
client_focus(master);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user