Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ struct platform {
* is called.
*/
void (*commit)();

/*
* Display an error modal dialog to the user.
* This function should block until the user dismisses the dialog
* or a timeout occurs (recommended: 10 seconds).
*/
void (*show_error_modal)(const char *title, const char *message);
};

void platform_run(int (*main) (struct platform *platform));
Expand Down
104 changes: 103 additions & 1 deletion src/platform/linux/X/X.c
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,9 @@ void x_init(struct platform *platform)
{
dpy = XOpenDisplay(NULL);
if (!dpy) {
fprintf(stderr, "Could not connect to X server\n");
/* Since we can't show a modal without X11, just use fprintf */
fprintf(stderr, "ERROR: Could not connect to X server\n");
fprintf(stderr, "Please make sure X11 is running and DISPLAY is set correctly.\n");
exit(-1);
}

Expand Down Expand Up @@ -231,4 +233,104 @@ void x_init(struct platform *platform)
platform->screen_get_dimensions = x_screen_get_dimensions;
platform->screen_list = x_screen_list;
platform->scroll = x_scroll;
platform->show_error_modal = x_show_error_modal;
}

void x_show_error_modal(const char *title, const char *message)
{
if (!dpy)
return;

Window root = DefaultRootWindow(dpy);
int screen = DefaultScreen(dpy);

/* Create a simple modal window */
int win_w = 400;
int win_h = 150;
int scr_w = DisplayWidth(dpy, screen);
int scr_h = DisplayHeight(dpy, screen);
int x = (scr_w - win_w) / 2;
int y = (scr_h - win_h) / 2;

Window win = XCreateSimpleWindow(dpy, root, x, y, win_w, win_h, 2,
BlackPixel(dpy, screen),
WhitePixel(dpy, screen));

/* Set window properties */
XStoreName(dpy, win, title);
XSelectInput(dpy, win, ExposureMask | KeyPressMask | ButtonPressMask);

/* Set window type to dialog */
Atom wm_type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
Atom wm_type_dialog = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False);
XChangeProperty(dpy, win, wm_type, XA_ATOM, 32, PropModeReplace,
(unsigned char *)&wm_type_dialog, 1);

/* Set it to be on top */
Atom wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
Atom wm_state_above = XInternAtom(dpy, "_NET_WM_STATE_ABOVE", False);
XChangeProperty(dpy, win, wm_state, XA_ATOM, 32, PropModeReplace,
(unsigned char *)&wm_state_above, 1);

XMapWindow(dpy, win);
XRaiseWindow(dpy, win);
XFlush(dpy);

/* Draw the message */
GC gc = XCreateGC(dpy, win, 0, NULL);
XSetForeground(dpy, gc, BlackPixel(dpy, screen));

/* Simple text rendering - split message into lines */
int text_y = 30;
const char *line_start = message;
char line_buf[256];

while (*line_start) {
const char *line_end = strchr(line_start, '\n');
int line_len;

if (line_end) {
line_len = line_end - line_start;
if (line_len > 255) line_len = 255;
strncpy(line_buf, line_start, line_len);
line_buf[line_len] = '\0';
line_start = line_end + 1;
} else {
strncpy(line_buf, line_start, 255);
line_buf[255] = '\0';
line_start += strlen(line_start);
}

XDrawString(dpy, win, gc, 10, text_y, line_buf, strlen(line_buf));
text_y += 20;

if (!*line_start)
break;
}

XDrawString(dpy, win, gc, 10, win_h - 20, "Press any key or click to dismiss", 34);
XFlush(dpy);

/* Wait for user input with 10 second timeout */
struct timeval tv;
fd_set fds;
int x11_fd = ConnectionNumber(dpy);

tv.tv_sec = 10;
tv.tv_usec = 0;

FD_ZERO(&fds);
FD_SET(x11_fd, &fds);

int ret = select(x11_fd + 1, &fds, NULL, NULL, &tv);

if (ret > 0) {
/* Wait for any event */
XEvent ev;
XNextEvent(dpy, &ev);
}

XFreeGC(dpy, gc);
XDestroyWindow(dpy, win);
XFlush(dpy);
}
1 change: 1 addition & 0 deletions src/platform/linux/X/X.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ void x_copy_selection();
void x_commit();
void x_monitor_file(const char *path);
long x_get_mtime(const char *path);
void x_show_error_modal(const char *title, const char *message);

extern struct monitored_file monitored_files[32];
extern size_t nr_monitored_files;
Expand Down
10 changes: 8 additions & 2 deletions src/platform/linux/X/input.c
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,15 @@ static uint8_t process_xinput_event(XEvent *ev, int *state, int *mods)
static const char *xerr_key = NULL;
static int input_xerr(Display *dpy, XErrorEvent *ev)
{
fprintf(stderr,
"ERROR: Failed to grab %s (ensure it isn't mapped by another application)\n",
char error_msg[256];
snprintf(error_msg, sizeof(error_msg),
"Failed to grab key: %s\n\n"
"Make sure it isn't mapped by another application.",
xerr_key);

/* Show error modal */
x_show_error_modal("Keyboard Grab Failed", error_msg);

return 0;
}

Expand Down
89 changes: 89 additions & 0 deletions src/platform/linux/wayland/wayland.c
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,93 @@ void wayland_init(struct platform *platform)
platform->screen_get_dimensions = way_screen_get_dimensions;
platform->screen_list = way_screen_list;
platform->scroll = way_scroll;
platform->show_error_modal = way_show_error_modal;
}

void way_show_error_modal(const char *title, const char *message)
{
/* For Wayland, we'll create a simple overlay window with the error message */
if (nr_screens == 0 || !wl.dpy)
return;

struct screen *scr = &screens[0];
int win_w = 400;
int win_h = 150;
int x = (scr->w - win_w) / 2;
int y = (scr->h - win_h) / 2;

/* Create a surface for the error modal */
struct surface *sfc = create_surface(scr, x, y, win_w, win_h, 1);
if (!sfc)
return;

/* Get cairo context from screen */
cairo_t *cr = scr->cr;
if (!cr)
return;

/* Draw white background */
cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
cairo_rectangle(cr, x, y, win_w, win_h);
cairo_fill(cr);

/* Draw border */
cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
cairo_set_line_width(cr, 2.0);
cairo_rectangle(cr, x, y, win_w, win_h);
cairo_stroke(cr);

/* Draw title */
cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size(cr, 14.0);
cairo_move_to(cr, x + 10, y + 25);
cairo_show_text(cr, title);

/* Draw message */
cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(cr, 12.0);

int text_y = y + 50;
const char *line_start = message;
char line_buf[256];

while (*line_start) {
const char *line_end = strchr(line_start, '\n');
int line_len;

if (line_end) {
line_len = line_end - line_start;
if (line_len > 255) line_len = 255;
strncpy(line_buf, line_start, line_len);
line_buf[line_len] = '\0';
line_start = line_end + 1;
} else {
strncpy(line_buf, line_start, 255);
line_buf[255] = '\0';
line_start += strlen(line_start);
}

cairo_move_to(cr, x + 10, text_y);
cairo_show_text(cr, line_buf);
text_y += 20;

if (!*line_start)
break;
}

/* Draw dismiss hint */
cairo_move_to(cr, x + 10, y + win_h - 20);
cairo_show_text(cr, "Will auto-dismiss in 10 seconds...");

surface_show(sfc);
wl_display_flush(wl.dpy);

/* Wait for 10 seconds */
sleep(10);

destroy_surface(sfc);
wl_display_flush(wl.dpy);
}
1 change: 1 addition & 0 deletions src/platform/linux/wayland/wayland.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,5 +126,6 @@ void way_copy_selection();
void way_commit();
void way_init();
void init_input();
void way_show_error_modal(const char *title, const char *message);

#endif
8 changes: 5 additions & 3 deletions src/platform/macos/input.m
Original file line number Diff line number Diff line change
Expand Up @@ -499,9 +499,11 @@ void macos_init_input()


if (!tap) {
fprintf(stderr,
"Failed to create event tap, make sure warpd is "
"whitelisted as an accessibility feature.\n");
/* Show error modal before exiting */
osx_show_error_modal("Accessibility Permission Required",
"Failed to create event tap.\n\n"
"Please make sure warpd is whitelisted as an\n"
"accessibility feature in System Settings.");
exit(-1);
}

Expand Down
1 change: 1 addition & 0 deletions src/platform/macos/macos.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,6 @@ void osx_copy_selection();
void osx_monitor_file(const char *_path);
void osx_input_interrupt();
void osx_commit();
void osx_show_error_modal(const char *title, const char *message);

#endif
19 changes: 19 additions & 0 deletions src/platform/macos/macos.m
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ void osx_commit()
.screen_list = osx_screen_list,
.scroll = osx_scroll,
.monitor_file = osx_monitor_file,
.show_error_modal = osx_show_error_modal,
};

main(&platform);
Expand All @@ -208,3 +209,21 @@ void platform_run(int (*main)(struct platform *platform))

[NSApp run];
}

void osx_show_error_modal(const char *title, const char *message)
{
dispatch_async(dispatch_get_main_queue(), ^{
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:[NSString stringWithUTF8String:title]];
[alert setInformativeText:[NSString stringWithUTF8String:message]];
[alert setAlertStyle:NSAlertStyleCritical];
[alert addButtonWithTitle:@"OK"];

/* Run the alert modally with a timeout */
[alert runModal];
});

/* Give the modal time to display */
sleep(1);
}

7 changes: 7 additions & 0 deletions src/platform/windows/windows.c
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,13 @@ void platform_run(int (*main)(struct platform *platform))
platform.input_lookup_code = input_lookup_code;
platform.input_lookup_name = input_lookup_name;
platform.monitor_file = wn_monitor_file;
platform.show_error_modal = wn_show_error_modal;

exit(main(&platform));
}

void wn_show_error_modal(const char *title, const char *message)
{
/* Use Windows MessageBox API for error display */
MessageBoxA(NULL, message, title, MB_OK | MB_ICONERROR | MB_TOPMOST);
}
1 change: 1 addition & 0 deletions src/platform/windows/windows.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ void wn_screen_get_dimensions(struct screen *scr, int *xoff, int *yoff, int *w,
void wn_screen_set_hints(struct screen *scr, struct hint *hints, size_t nhints);
void wn_screen_set_hintinfo(COLORREF _hint_bgcol, COLORREF _hint_fgcol);
void wn_monitor_file(const char *path);
void wn_show_error_modal(const char *title, const char *message);

#endif
11 changes: 11 additions & 0 deletions src/warpd.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ uint64_t get_time_us()
return ts.tv_nsec / 1E3 + ts.tv_sec * 1E6;
}

void show_error_modal(const char *title, const char *message)
{
/* Log to stderr as well for debugging/logging purposes */
fprintf(stderr, "ERROR: %s: %s\n", title, message);

/* Show modal if platform is initialized and supports it */
if (platform && platform->show_error_modal) {
platform->show_error_modal(title, message);
}
}


const char *get_data_path(const char *file)
{
Expand Down
2 changes: 2 additions & 0 deletions src/warpd.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,5 +148,7 @@ extern struct config_entry *config;
int mode_loop(int initial_mode, int oneshot, int record_history);
void daemon_loop(const char *config_path);

void show_error_modal(const char *title, const char *message);

extern struct platform *platform;
#endif
Loading