This patch adds optional support for compressed files and a new --ignore-unknown option to ignore unknown image format. It also adds local filename drag-and-drop support. And a copy current image to clipboard feature with ctrl+c shortcut.
It also fixes:
- fullscreen issue on multi monitors setup by using randr
- app icon advertised via _NET_WM_ICON
- fix UTF-8 filename usage in window title
---
configure.ac | 16 +
doc/wmiv.1 | 43 +-
util/Makefile.am | 5 +-
util/wmiv.c | 1767 +++++++++++++++++++++++++++++++++++++++-------
util/xdnd.c | 1596 +++++++++++++++++++++++++++++++++++++++++
util/xdnd.h | 219 ++++++
6 files changed, 3376 insertions(+), 270 deletions(-)
create mode 100644 util/xdnd.c
create mode 100644 util/xdnd.h
diff --git a/
configure.ac b/
configure.acindex ad67fc91..86ac2a00 100644
--- a/
configure.ac+++ b/
configure.ac@@ -857,6 +857,22 @@ AM_CONDITIONAL([ICON_EXT_XPM], [test "x$ICONEXT" = "xxpm"])
AM_CONDITIONAL([ICON_EXT_TIFF], [test "x$ICONEXT" = "xtiff"])
+dnl Archive Support for wmiv
+dnl =================================
+dnl Check for libarchive (comprehensive archive support)
+AC_CHECK_LIB([archive], [archive_read_new], [
+ AC_CHECK_HEADER([archive.h], [
+ AC_DEFINE([HAVE_LIBARCHIVE], [1], [Define if libarchive is available])
+ LIBARCHIVE_LIBS="-larchive"
+ ], [
+ AC_MSG_WARN([libarchive header not found, archive support disabled])
+ ])
+], [
+ AC_MSG_WARN([libarchive not found, archive support disabled])
+])
+AC_SUBST([LIBARCHIVE_LIBS])
+
+
dnl ==============================================
dnl End of Graphic Format Libraries
dnl ==============================================
diff --git a/doc/wmiv.1 b/doc/wmiv.1
index 924c317f..0c66e90e 100644
--- a/doc/wmiv.1
+++ b/doc/wmiv.1
@@ -3,14 +3,17 @@
wmiv \- quick image viewer using wrlib
.SH SYNOPSIS
.B wmiv
-.RI [ \,image(s)\/ | \,directory\/ ]
+.RI [ \,image(s)\/ | \,directory\/ | \,archive\/]
.SH OPTIONS
.TP
-.B \-\-help
+.B \-h, \-\-help
print help text
.TP
-.B \-\-version
+.B \-v, \-\-version
print version
+.TP
+.B \-i, \-\-ignore-unknown
+ignore unknown image format
.SH KEYS
.TP
[+]
@@ -19,11 +22,29 @@ zoom in
[\-]
zoom out
.TP
+[▸]
+next image
+.TP
+[◂]
+previous image
+.TP
+[▴]
+first image
+.TP
+[▾]
+last image
+.TP
+[Ctrl+C]
+copy image to clipboard
+.TP
+[D]
+start slideshow
+.TP
[Esc]
actual size
.TP
-[D]
-launch diaporama mode
+[F]
+toggle full-screen mode
.TP
[L]
rotate image on the left
@@ -33,18 +54,6 @@ quit
.TP
[R]
rotate image on the right
-.TP
-[▸]
-next image
-.TP
-[◂]
-previous image
-.TP
-[▴]
-first image
-.TP
-[▾]
-last image
.SH AUTHOR
.B wmiv
is a part of Window Maker. It was written by David Maciejak.
diff --git a/util/Makefile.am b/util/Makefile.am
index 0c148702..ef9e586e 100644
--- a/util/Makefile.am
+++ b/util/Makefile.am
@@ -76,9 +76,10 @@ wmiv_LDADD = \
$(top_builddir)/wrlib/
libwraster.la \
$(top_builddir)/WINGs/libWINGs.la \
@XLFLAGS@ @XLIBS@ @GFXLIBS@ \
-
@PANGO_LIBS@ @PTHREAD_LIBS@ @LIBEXIF@
+
@PANGO_LIBS@ @PTHREAD_LIBS@ @LIBEXIF@ \
+
@LIBARCHIVE_LIBS@ @LIBM@ @LIBXRANDR@
-wmiv_SOURCES = wmiv.c wmiv.h
+wmiv_SOURCES = wmiv.c wmiv.hi xdnd.c xdnd.h
CLEANFILES = wmaker.inst
diff --git a/util/wmiv.c b/util/wmiv.c
index 0d33a535..47290501 100755
--- a/util/wmiv.c
+++ b/util/wmiv.c
@@ -1,7 +1,7 @@
/*
* Window Maker window manager
*
- * Copyright (c) 2014-2023 Window Maker Team - David Maciejak
+ * Copyright (c) 2014-2026 Window Maker Team - David Maciejak
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -33,9 +33,13 @@
#include <limits.h>
#include <unistd.h>
#include <sys/stat.h>
+#include <errno.h>
#include <getopt.h>
#include <math.h>
+#include <ftw.h>
+#include <libgen.h>
#include "config.h"
+#include "xdnd.h"
#ifdef HAVE_EXIF
#include <libexif/exif-data.h>
@@ -45,23 +49,45 @@
#include <pthread.h>
#endif
+#ifdef HAVE_LIBARCHIVE
+#include <archive.h>
+#include <archive_entry.h>
+#endif
+
+#ifdef USE_RANDR
+#include <X11/extensions/Xrandr.h>
+#endif
+
#ifdef USE_XPM
extern int XpmCreatePixmapFromData(Display *, Drawable, char **, Pixmap *, Pixmap *, void *);
-/*
this is the icon from eog project
-
git.gnome.org/browse/eog-*/
+/*
This is the icon from the eog project
git.gnome.org/browse/eog */
#include "wmiv.h"
#endif
+#ifdef DEBUG
+#define WMIV_DEBUG 1
+#else
#define WMIV_DEBUG 0
+#endif
+
#define FILE_SEPARATOR '/'
+/* CHUNK size for INCR (bytes). Tune if desired. */
+#define CHUNK_SIZE 65536
+
Display *dpy;
Window win;
RContext *ctx;
RImage *img;
Pixmap pix;
+/* DND support for sending drops */
+static DndClass send_dnd;
+static Bool dnd_initialized = False;
+static Bool button1_pressed = False;
+static int drag_start_x, drag_start_y;
+static int drag_threshold = 5; /* pixels */
+
const char *APPNAME = "wmiv";
int NEXT = 0;
int PREV = 1;
@@ -69,13 +95,18 @@ float zoom_factor = 0;
int max_width = 0;
int max_height = 0;
+/* Archive support */
+static char *temp_dir = NULL;
+static Bool is_archive = False;
+
Bool fullscreen_flag = False;
-Bool focus = False;
+Bool focus = True;
Bool back_from_fullscreen = False;
+Bool ignore_unknown_file_format = False;
#ifdef HAVE_PTHREAD
-Bool diaporama_flag = False;
-int diaporama_delay = 5;
+Bool slideshow_flag = False;
+int slideshow_delay = 5;
pthread_t tid = 0;
#endif
XTextProperty title_property;
@@ -88,6 +119,16 @@ RColor darkGray;
RColor black;
RColor red;
+/* Structure to hold the frame extents */
+typedef struct {
+
unsigned long left;
+
unsigned long right;
+
unsigned long top;
+
unsigned long bottom;
+} FrameExtents;
+
+FrameExtents extents = {0, 0, 0, 0};
+
typedef struct link link_t;
struct link {
const void *data;
@@ -104,11 +145,755 @@ typedef struct linked_list {
linked_list_t list;
link_t *current_link;
+/* --- Clipboard/PNG + JPEG + INCR globals --- */
+static Atom clipboard_atom = None;
+static Atom targets_atom = None;
+static Atom png_atom = None;
+static Atom jpeg_atom = None;
+static Atom property_atom = None;
+static Atom incr_atom = None;
+
+static unsigned char *clipboard_png_data = NULL;
+static size_t clipboard_png_size = 0;
+static unsigned char *clipboard_jpeg_data = NULL;
+static size_t clipboard_jpeg_size = 0;
+static Bool we_own_clipboard = False;
+
+/* INCR transfer state */
+typedef struct {
+
Bool active;
+
Window requestor;
+
Atom property; /* property name the requestor asked for (req->property) */
+
Atom target; /* requested target (png_atom or jpeg_atom) */
+
size_t total_size;
+
size_t offset;
+
size_t chunk_size;
+
long saved_event_mask; /* to restore previous event mask on requestor window */
+
unsigned char *data; /* pointer to either PNG or JPEG data */
+} incr_transfer_t;
+
+static incr_transfer_t incr = { .active = False };
+
+/* End clipboard globals */
+
+/*
+
Wait for the window manager to set the _NET_FRAME_EXTENTS property
+
by listening for PropertyNotify events
+
Returns 1 on success, 0 on timeout
+*/
+int wait_for_frame_extents(Display *disp, Window win, int timeout_ms) {
+
Atom net_frame_extents = XInternAtom(disp, "_NET_FRAME_EXTENTS", False);
+
if (net_frame_extents == None)
+
return 0;
+
+
XEvent event;
+
int elapsed = 0;
+
const int poll_interval = 10; /* milliseconds */
+
+
while (elapsed < timeout_ms) {
+
/* Check if property is already available */
+
Atom actual_type;
+
int actual_format;
+
unsigned long num_items;
+
unsigned long bytes_after;
+
unsigned char *prop_return = NULL;
+
+
int status = XGetWindowProperty(disp, win, net_frame_extents, 0L, 1L, False, XA_CARDINAL,
+
&actual_type, &actual_format, &num_items, &bytes_after, &prop_return);
+
+
if (status == Success && actual_type == XA_CARDINAL && num_items >= 1) {
+
if (prop_return)
+
XFree(prop_return);
+
return 1;
+
}
+
if (prop_return) XFree(prop_return);
+
+
/* Wait for events or timeout */
+
if (XPending(disp)) {
+
XNextEvent(disp, &event);
+
if (event.type == PropertyNotify &&
+
event.xproperty.window == win &&
+
event.xproperty.atom == net_frame_extents &&
+
event.xproperty.state == PropertyNewValue) {
+
return 1;
+
}
+
}
+
+
usleep(poll_interval * 1000);
+
elapsed += poll_interval;
+
XFlush(disp);
+
}
+
+
return 0;
+}
+
+/*
+
Get the frame extents (decorations) of a window
+
Returns 1 on success, 0 on failure
+*/
+int get_window_decor_size(Display *disp, Window win, FrameExtents *extents) {
+
Atom actual_type;
+
int actual_format;
+
unsigned long num_items;
+
unsigned long bytes_after;
+
unsigned char *prop_return = NULL;
+
Atom net_frame_extents;
+
int status;
+
+
/* Get the atom for the _NET_FRAME_EXTENTS property */
+
net_frame_extents = XInternAtom(disp, "_NET_FRAME_EXTENTS", False);
+
if (net_frame_extents == None) {
+
fprintf(stderr, "Warning: _NET_FRAME_EXTENTS atom not supported\n");
+
return 0;
+
}
+
+
/* Retrieve the property value */
+
status = XGetWindowProperty(disp, win, net_frame_extents, 0L, 4L, False, XA_CARDINAL,
+
&actual_type, &actual_format, &num_items, &bytes_after, &prop_return);
+
+
if (status == Success && actual_type == XA_CARDINAL && actual_format == 32 && num_items == 4) {
+
unsigned long *extents_data = (unsigned long *)prop_return;
+
extents->left = extents_data[0];
+
extents->right = extents_data[1];
+
extents->top = extents_data[2];
+
extents->bottom = extents_data[3];
+
+
if (WMIV_DEBUG) {
+
fprintf(stderr, "Successfully got frame extents: left=%lu, right=%lu, top=%lu, bottom=%lu\n",
+
extents->left, extents->right, extents->top, extents->bottom);
+
}
+
+
XFree(prop_return);
+
return 1;
+
}
+
+
if (prop_return)
+
XFree(prop_return);
+
+
if (status != Success) {
+
if (WMIV_DEBUG) {
+
fprintf(stderr, "Error: XGetWindowProperty failed with status: %d\n", status);
+
}
+
} else if (actual_type != XA_CARDINAL) {
+
if (WMIV_DEBUG) {
+
fprintf(stderr, "Error: wrong property type: got %lu, expected %lu (XA_CARDINAL)\n",
+
(unsigned long)actual_type, (unsigned long)XA_CARDINAL);
+
}
+
} else if (actual_format != 32) {
+
if (WMIV_DEBUG) {
+
fprintf(stderr, "Error: wrong property format: got %d, expected 32\n", actual_format);
+
}
+
} else if (num_items != 4) {
+
if (WMIV_DEBUG) {
+
fprintf(stderr, "Error: wrong number of items: got %lu, expected 4\n", num_items);
+
}
+
}
+
+
return 0;
+}
+
+#ifdef HAVE_LIBARCHIVE
+/*
+
Create directory path recursively
+
Returns 0 on success, -1 on failure
+*/
+static int create_directories(const char *path)
+{
+
char *path_copy = strdup(path);
+
char *p;
+
int result = 0;
+
+
if (!path_copy)
+
return -1;
+
+
/* Skip leading slash if present */
+
p = path_copy;
+
if (*p == '/')
+
p++;
+
+
/* Create each directory component */
+
while ((p = strchr(p, '/')) != NULL) {
+
*p = '\0';
+
if (mkdir(path_copy, 0755) != 0 && errno != EEXIST) {
+
result = -1;
+
break;
+
}
+
*p = '/';
+
p++;
+
}
+
+
/* Create final directory component */
+
if (result == 0 && mkdir(path_copy, 0755) != 0 && errno != EEXIST)
+
result = -1;
+
+
free(path_copy);
+
return result;
+}
+#endif
+
+/*
+
Callback function for nftw to remove files and directories
+*/
+static int remove_temp_file(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
+{
+
(void)sb;
+
(void)typeflag;
+
(void)ftwbuf;
+
+
return remove(fpath);
+}
+
+/*
+
Remove the temporary directory and its contents
+*/
+static void cleanup_temp_dir(void)
+{
+
if (temp_dir) {
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Cleaning up temporary directory: %s\n", temp_dir);
+
nftw(temp_dir, remove_temp_file, 64, FTW_DEPTH | FTW_PHYS);
+
free(temp_dir);
+
temp_dir = NULL;
+
}
+
is_archive = False;
+}
+
+/*
+
Check if file has archive extension
+
Returns True if it is an archive, False otherwise
+*/
+static Bool is_archive_file(const char *filename)
+{
+
const char *ext = strrchr(filename, '.');
+
+
return (ext && (strcasecmp(ext, ".cbz") == 0 ||
+
strcasecmp(ext, ".zip") == 0 ||
+
strcasecmp(ext, ".cbr") == 0 ||
+
strcasecmp(ext, ".rar") == 0 ||
+
strcasecmp(ext, ".cbt") == 0 ||
+
strcasecmp(ext, ".cb7") == 0 ||
+
strcasecmp(ext, ".tar") == 0 ||
+
strcasecmp(ext, ".7z") == 0 ||
+
strcasecmp(ext, ".tar.gz") == 0 ||
+
strcasecmp(ext, ".tar.bz2") == 0));
+}
+
+#ifdef HAVE_LIBARCHIVE
+
+/*
+
Recursively find directory containing image files
+
Returns path to directory with images, or NULL if not found
+*/
+static char *find_image_directory(const char *start_dir)
+{
+
DIR *dir;
+
struct dirent *entry;
+
struct stat st;
+
char full_path[PATH_MAX];
+
int image_count = 0;
+
char *result = NULL;
+
+
dir = opendir(start_dir);
+
if (!dir)
+
return NULL;
+
+
+
/* First pass: count image files in current directory */
+
while ((entry = readdir(dir)) != NULL) {
+
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
+
continue;
+
+
snprintf(full_path, sizeof(full_path), "%s/%s", start_dir, entry->d_name);
+
if (stat(full_path, &st) == 0 && S_ISREG(st.st_mode))
+
image_count++;
+
}
+
closedir(dir);
+
+
/* If current directory has images, return it */
+
if (image_count > 0) {
+
return strdup(start_dir);
+
}
+
+
/* Second pass: recursively check subdirectories */
+
dir = opendir(start_dir);
+
if (!dir)
+
return NULL;
+
+
while ((entry = readdir(dir)) != NULL) {
+
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
+
continue;
+
+
snprintf(full_path, sizeof(full_path), "%s/%s", start_dir, entry->d_name);
+
if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {
+
result = find_image_directory(full_path);
+
if (result)
+
break;
+
}
+
}
+
closedir(dir);
+
return result;
+}
+
+/*
+
Extract archive using libarchive
+
Returns 0 on success, -1 on failure
+*/
+static int extract_archive_libarchive(const char *filename, const char *dest_dir)
+{
+
struct archive *a;
+
struct archive_entry *entry;
+
char dest_path[PATH_MAX];
+
char *buffer = NULL;
+
const size_t buffer_size = 8192;
+
int r;
+
+
/* Create archive reader */
+
a = archive_read_new();
+
if (!a) {
+
fprintf(stderr, "Error: failed to create archive reader\n");
+
return -1;
+
}
+
+
/* Enable support for all formats and filters */
+
archive_read_support_filter_all(a);
+
archive_read_support_format_all(a);
+
+
/* Open the archive file */
+
r = archive_read_open_filename(a, filename, 10240);
+
if (r != ARCHIVE_OK) {
+
fprintf(stderr, "Error: failed to open archive: %s\n", filename);
+
archive_read_free(a);
+
return -1;
+
}
+
+
buffer = malloc(buffer_size);
+
if (!buffer) {
+
fprintf(stderr, "Error: failed to allocate memory\n");
+
archive_read_free(a);
+
return -1;
+
}
+
+
/* Extract each entry */
+
while ((r = archive_read_next_header(a, &entry)) == ARCHIVE_OK) {
+
const char *entry_name = archive_entry_pathname(entry);
+
la_int64_t entry_size = archive_entry_size(entry);
+
mode_t entry_mode = archive_entry_mode(entry);
+
FILE *output_file;
+
la_ssize_t bytes_read;
+
+
if (!entry_name)
+
continue;
+
+
/* Skip directories and special files */
+
if (!S_ISREG(entry_mode) || entry_size <= 0)
+
continue;
+
+
/* Create destination path */
+
snprintf(dest_path, sizeof(dest_path), "%s/%s", dest_dir, entry_name);
+
+
/* Create directory structure if needed */
+
char *dir_end = strrchr(dest_path, '/');
+
if (dir_end) {
+
*dir_end = '\0';
+
if (create_directories(dest_path) != 0) {
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Warning: failed to create directory: %s\n", dest_path);
+
}
+
*dir_end = '/';
+
}
+
+
/* Create output file */
+
output_file = fopen(dest_path, "wb");
+
if (!output_file) {
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Warning: failed to create file: %s\n", dest_path);
+
continue;
+
}
+
+
/* Extract file content */
+
while ((bytes_read = archive_read_data(a, buffer, buffer_size)) > 0) {
+
fwrite(buffer, 1, bytes_read, output_file);
+
}
+
+
if (bytes_read < 0) {
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Warning: failed to read data for: %s\n", entry_name);
+
}
+
+
fclose(output_file);
+
+
//if (WMIV_DEBUG)
+
//
fprintf(stderr, "Extracted %s\n", entry_name);
+
}
+
+
if (r != ARCHIVE_EOF)
+
fprintf(stderr, "Warning: archive reading ended with error\n");
+
+
free(buffer);
+
archive_read_free(a);
+
+
return 0;
+}
+#endif
+
+/*
+
Extract archive to temporary directory
+
Returns path to extracted directory on success, NULL on failure
+*/
+static char *extract_archive(const char *filename)
+{
+#ifdef HAVE_LIBARCHIVE
+
char *temp_template = NULL;
+
int result;
+
+
/* Create temporary directory template */
+
if (asprintf(&temp_template, "/tmp/wmiv_XXXXXX") == -1) {
+
fprintf(stderr, "Error: failed to allocate memory\n");
+
return NULL;
+
}
+
+
/* Create temporary directory */
+
temp_dir = mkdtemp(temp_template);
+
if (!temp_dir) {
+
perror("mkdtemp");
+
free(temp_template);
+
return NULL;
+
}
+
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Created temporary directory: %s\n", temp_dir);
+
+
/* Extract the archive */
+
if (is_archive_file(filename)) {
+
result = extract_archive_libarchive(filename, temp_dir);
+
} else {
+
fprintf(stderr, "Error: unknown archive type\n");
+
cleanup_temp_dir();
+
return NULL;
+
}
+
+
if (result != 0) {
+
cleanup_temp_dir();
+
return NULL;
+
}
+
+
/* Check if extraction resulted in nested directories - find the one with actual images */
+
char *image_dir = find_image_directory(temp_dir);
+
if (image_dir && strcmp(image_dir, temp_dir) != 0) {
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Archive contains nested directories, using image directory: %s\n", image_dir);
+
+
char *result_dir = strdup(image_dir);
+
free(image_dir);
+
is_archive = True;
+
return result_dir;
+
}
+
+
if (image_dir)
+
free(image_dir);
+
+
is_archive = True;
+
return strdup(temp_dir);
+#else
+
(void) filename;
+
fprintf(stderr, "Error: archive support not available\n");
+
+
return NULL;
+#endif
+}
+
+/*
+
Convert current RImage to specified format using RSaveRawImage
+
Returns 0 on success (buffer & size filled), -1 on failure
+*/
+static int convert_image_to_format(RImage *image, const char *format, unsigned char **out_buf, size_t *out_size)
+{
+
if (!image || !format || !out_buf || !out_size) {
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Error: convert_image_to_format invalid parameters\n");
+
return -1;
+
}
+
+
if (!RSaveRawImage(image, format, out_buf, out_size)) {
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Error: RSaveRawImage failed for %s, RErrorCode=%d\n", format, RErrorCode);
+
return -1;
+
}
+
+
return 0;
+}
+
+/*
+
Free current clipboard data (both PNG and JPEG)
+*/
+static void free_clipboard_data(void)
+{
+
if (clipboard_png_data) {
+
free(clipboard_png_data);
+
clipboard_png_data = NULL;
+
}
+
clipboard_png_size = 0;
+
if (clipboard_jpeg_data) {
+
free(clipboard_jpeg_data);
+
clipboard_jpeg_data = NULL;
+
}
+
clipboard_jpeg_size = 0;
+}
+
+/*
+
Send the next chunk for INCR transfer (called when requestor deletes the property)
+*/
+static void send_next_incr_chunk(void)
+{
+
size_t remaining, to_send;
+
+
if (!incr.active)
+
return;
+
+
if (!incr.data || incr.total_size == 0) {
+
/* Nothing to send: finish with zero-length property */
+
XChangeProperty(dpy, incr.requestor, incr.property, incr.target, 8, PropModeReplace, NULL, 0);
+
/* Clean up: remove PropertyChangeMask from requestor window */
+
XSelectInput(dpy, incr.requestor, NoEventMask);
+
incr.active = False;
+
we_own_clipboard = False;
+
return;
+
}
+
+
remaining = incr.total_size - incr.offset;
+
to_send = remaining;
+
+
if (to_send > incr.chunk_size)
+
to_send = incr.chunk_size;
+
+
XChangeProperty(dpy, incr.requestor, incr.property, incr.target, 8, PropModeReplace,
+
incr.data + incr.offset, (int)to_send);
+
XFlush(dpy);
+
+
incr.offset += to_send;
+
+
if (incr.offset >= incr.total_size) {
+
/* finished: next deletion by client -> we'll send zero-length and finish */
+
if (WMIV_DEBUG)
+
fprintf(stderr, "INCR: all data sent (%zu bytes), awaiting final property deletion\n", incr.total_size);
+
} else {
+
if (WMIV_DEBUG)
+
fprintf(stderr, "INCR: sent chunk %zu/%zu\n", incr.offset, incr.total_size);
+
}
+}
+
+/*
+
Called when Ctrl+C pressed: encode current image to PNG and JPEG and claim CLIPBOARD
+*/
+static void copy_current_image_to_clipboard(void)
+{
+
if (!dpy || !win || !img) return;
+
+
/* free previous */
+
free_clipboard_data();
+
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Attempting to copy image to clipboard (size: %dx%d)\n", img->width, img->height);
+
+
/* Convert image to both formats */
+#ifdef USE_PNG
+
if (convert_image_to_format(img, "PNG", &clipboard_png_data, &clipboard_png_size) != 0) {
+
fprintf(stderr, "Error: failed to convert image to PNG\n");
+
return;
+
}
+#endif
+#ifdef USE_JPEG
+
if (convert_image_to_format(img, "JPEG", &clipboard_jpeg_data, &clipboard_jpeg_size) != 0) {
+
fprintf(stderr, "Error: failed to convert image to JPEG\n");
+
free_clipboard_data();
+
return;
+
}
+#endif
+
if (clipboard_png_data == NULL && clipboard_jpeg_data == NULL) {
+
fprintf(stderr, "Error: no image formats available to copy to the clipboard\n");
+
return;
+
}
+
+
/* claim CLIPBOARD */
+
XSetSelectionOwner(dpy, clipboard_atom, win, CurrentTime);
+
if (XGetSelectionOwner(dpy, clipboard_atom) != win) {
+
fprintf(stderr, "Error: failed to own clipboard selection\n");
+
free_clipboard_data();
+
we_own_clipboard = False;
+
return;
+
}
+
we_own_clipboard = True;
+
XFlush(dpy);
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Copied image to clipboard (PNG: %zu bytes, JPEG: %zu bytes)\n", clipboard_png_size, clipboard_jpeg_size);
+}
+
+/*
+
Handle SelectionRequest events: serve TARGETS and image formats (with INCR when needed)
+*/
+static void handle_selection_request(XSelectionRequestEvent *req)
+{
+
if (WMIV_DEBUG) {
+
char *target_name = XGetAtomName(req->display, req->target);
+
char *selection_name = XGetAtomName(req->display, req->selection);
+
+
fprintf(stderr, "SelectionRequest received: target=%s, selection=%s\n",
+
target_name ? target_name : "unknown",
+
selection_name ? selection_name : "unknown");
+
if (target_name)
+
XFree(target_name);
+
if (selection_name)
+
XFree(selection_name);
+
}
+
+
XEvent ev;
+
memset(&ev, 0, sizeof(ev));
+
ev.xselection.type = SelectionNotify;
+
ev.xselection.display = req->display;
+
ev.xselection.requestor = req->requestor;
+
ev.xselection.selection = req->selection;
+
ev.xselection.target = req->target;
+
ev.xselection.time = req->time;
+
ev.xselection.property = None;
+
+
if (req->target == targets_atom) {
+
/* advertise supported targets (image/png and image/jpeg) */
+#if defined(USE_PNG) && defined(USE_JPEG)
+
Atom types[2];
+
types[0] = png_atom;
+
types[1] = jpeg_atom;
+#else
+#if defined(USE_JPEG)
+
Atom types[1];
+
types[0] = jpeg_atom;
+#else
+
Atom types[1];
+
types[0] = png_atom;
+#endif
+#endif
+
+
XChangeProperty(dpy, req->requestor, req->property, XA_ATOM, 32, PropModeReplace,
+
(unsigned char *)types, 2);
+
ev.xselection.property = req->property;
+
}
+
else if (req->target == png_atom) {
+
if (!clipboard_png_data || clipboard_png_size == 0) {
+
ev.xselection.property = None;
+
} else {
+
/* Decide whether to use INCR */
+
if (clipboard_png_size > CHUNK_SIZE) {
+
/* Start INCR transfer */
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Starting INCR for PNG %zu bytes\n", clipboard_png_size);
+
+
/* Prepare incr state */
+
incr.active = True;
+
incr.requestor = req->requestor;
+
incr.property = req->property;
+
incr.target = req->target;
+
incr.total_size = clipboard_png_size;
+
incr.offset = 0;
+
incr.chunk_size = CHUNK_SIZE;
+
incr.data = clipboard_png_data;
+
+
/* Announce INCR by writing a 32-bit size to the property of type INCR */
+
unsigned long size32 = (unsigned long)incr.total_size;
+
XChangeProperty(dpy, req->requestor, req->property, incr_atom, 32, PropModeReplace,
+
(unsigned char *)&size32, 1);
+
ev.xselection.property = req->property;
+
+
/* Send SelectionNotify now to tell client INCR was used */
+
XSendEvent(dpy, req->requestor, False, 0, (XEvent *)&ev);
+
XFlush(dpy);
+
+
/* Now select PropertyChangeMask on the requestor window to see property deletions.
+
The client should delete the property when ready for the first chunk. */
+
XSelectInput(dpy, req->requestor, PropertyChangeMask);
+
return; /* we already sent SelectionNotify above */
+
} else {
+
/* Small transfer: send whole PNG in one property set */
+
XChangeProperty(dpy, req->requestor, req->property, png_atom, 8, PropModeReplace,
+
clipboard_png_data, (int)clipboard_png_size);
+
ev.xselection.property = req->property;
+
}
+
}
+
}
+
else if (req->target == jpeg_atom) {
+
if (!clipboard_jpeg_data || clipboard_jpeg_size == 0) {
+
ev.xselection.property = None;
+
} else {
+
/* Decide whether to use INCR */
+
if (clipboard_jpeg_size > CHUNK_SIZE) {
+
/* Start INCR transfer */
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Starting INCR for JPEG %zu bytes\n", clipboard_jpeg_size);
+
+
/* Prepare incr state */
+
incr.active = True;
+
incr.requestor = req->requestor;
+
incr.property = req->property;
+
incr.target = req->target;
+
incr.total_size = clipboard_jpeg_size;
+
incr.offset = 0;
+
incr.chunk_size = CHUNK_SIZE;
+
incr.data = clipboard_jpeg_data;
+
+
/* Announce INCR by writing a 32-bit size to the property of type INCR */
+
unsigned long size32 = (unsigned long)incr.total_size;
+
XChangeProperty(dpy, req->requestor, req->property, incr_atom, 32, PropModeReplace,
+
(unsigned char *)&size32, 1);
+
ev.xselection.property = req->property;
+
+
/* Send SelectionNotify now to tell client INCR was used */
+
XSendEvent(dpy, req->requestor, False, 0, (XEvent *)&ev);
+
XFlush(dpy);
+
+
/* Now select PropertyChangeMask on the requestor window to see property deletions.
+
The client should delete the property when ready for the first chunk. */
+
XSelectInput(dpy, req->requestor, PropertyChangeMask);
+
return; /* we already sent SelectionNotify above */
+
} else {
+
/* Small transfer: send whole JPEG in one property set */
+
XChangeProperty(dpy, req->requestor, req->property, jpeg_atom, 8, PropModeReplace,
+
clipboard_jpeg_data, (int)clipboard_jpeg_size);
+
ev.xselection.property = req->property;
+
}
+
}
+
} else {
+
ev.xselection.property = None;
+
}
+
+
XSendEvent(dpy, req->requestor, False, 0, &ev);
+
XFlush(dpy);
+}
+
+/*
+
Called when the owner loses the selection
+*/
+static void handle_selection_clear(void)
+{
+
if (we_own_clipboard) {
+
we_own_clipboard = False;
+
free_clipboard_data();
+
}
+
if (incr.active) {
+
/* Cancel any active INCR transfer and clean up */
+
if (incr.requestor != None) {
+
/* Remove PropertyChangeMask from requestor window */
+
XSelectInput(dpy, incr.requestor, NoEventMask);
+
}
+
incr.active = False;
+
incr.offset = 0;
+
incr.total_size = 0;
+
incr.requestor = None;
+
incr.property = None;
+
incr.target = None;
+
}
+}
/*
-
load_oriented_image: used to load an image and optionally
-
get its orientation if libexif is available
-
return the image on success, NULL on failure
+
Load an image and optionally get its orientation if libexif is available
+
Returns the image on success, NULL on failure
*/
RImage *load_oriented_image(RContext *context, const char *file, int index)
{
@@ -189,16 +974,22 @@ RImage *load_oriented_image(RContext *context, const char *file, int index)
}
/*
-
change_title: used to change window title
-
return EXIT_SUCCESS on success, 1 on failure
+
Change window title
+
Returns EXIT_SUCCESS on success, EXIT_FAILURE on failure
*/
int change_title(XTextProperty *prop, char *filename)
{
char *combined_title = NULL;
-
if (!asprintf(&combined_title, "%s - %u/%u - %s", APPNAME, current_index, max_index, filename))
-
if (!asprintf(&combined_title, "%s - %u/%u", APPNAME, current_index, max_index))
+
if (ignore_unknown_file_format) {
+
if (!asprintf(&combined_title, "%s - %s", APPNAME, filename))
return EXIT_FAILURE;
-
XStringListToTextProperty(&combined_title, 1, prop);
+
}
+
else {
+
if (!asprintf(&combined_title, "%s - %u/%u - %s", APPNAME, current_index, max_index, filename))
+
if (!asprintf(&combined_title, "%s - %u/%u", APPNAME, current_index, max_index))
+
return EXIT_FAILURE;
+
}
+
Xutf8TextListToTextProperty(dpy, &combined_title, 1, XUTF8StringStyle, prop);
XSetWMName(dpy, win, prop);
if (prop->value)
XFree(prop->value);
@@ -207,38 +998,45 @@ int change_title(XTextProperty *prop, char *filename)
}
/*
-
rescale_image: used to rescale the current image based on the screen size
-
return EXIT_SUCCESS on success
+
Rescale the current image based on the screen size
+
Returns EXIT_SUCCESS on success, EXIT_FAILURE on failure
*/
int rescale_image(void)
{
long final_width = img->width;
long final_height = img->height;
+
int max_width_tmp = max_width;
+
int max_height_tmp = max_height;
+
+
if (!fullscreen_flag) {
+
max_width_tmp -= extents.left + extents.right;
+
max_height_tmp -= extents.top + extents.bottom;
+
}
/* check if there is already a zoom factor applied */
if (fabsf(zoom_factor) <= 0.0f) {
final_width = img->width + (int)(img->width * zoom_factor);
final_height = img->height + (int)(img->height * zoom_factor);
}
-
if ((max_width < final_width) || (max_height < final_height)) {
-
long val = 0;
+
if ((max_width_tmp < final_width) || (max_height_tmp < final_height)) {
+
double val = 0;
if (final_width > final_height) {
-
val = final_height * max_width / final_width;
-
final_width = final_width * val / final_height;
-
final_height = val;
-
if (val > max_height) {
-
val = final_width * max_height / final_height;
-
final_height = final_height * val / final_width;
-
final_width = val;
+
val = final_height * max_width_tmp / final_width;
+
final_width = ceil(final_width * val / final_height);
+
final_height = ceil(val);
+
if (val > max_height_tmp) {
+
val = final_width * max_height_tmp / final_height;
+
final_height = ceil(final_height * val / final_width);
+
final_width = ceil(val);
}
} else {
-
val = final_width * max_height / final_height;
-
final_height = final_height * val / final_width;
-
final_width = val;
-
if (val > max_width) {
-
val = final_height * max_width / final_width;
-
final_width = final_width * val / final_height;
-
final_height = val;
+
val = final_width * max_height_tmp / final_height;
+
final_height = ceil(final_height * val / final_width);
+
final_width = ceil(val);
+
if (val > max_width_tmp) {
+
val = final_height * max_width_tmp / final_width;
+
final_width = ceil(final_width * val / final_height);
+
final_height = ceil(val);
}
}
}
@@ -252,15 +1050,15 @@ int rescale_image(void)
RReleaseImage(old_img);
}
if (!RConvertImage(ctx, img, &pix)) {
-
fprintf(stderr, "%s\n", RMessageForError(RErrorCode));
+
fprintf(stderr, "Error: %s\n", RMessageForError(RErrorCode));
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
/*
-
maximize_image: find the best image size for the current display
-
return EXIT_SUCCESS on success
+
Find the best image size for the current display
+
Returns EXIT_SUCCESS on success
*/
int maximize_image(void)
{
@@ -271,8 +1069,8 @@ int maximize_image(void)
}
/*
-
merge_with_background: merge the current image with with a checkboard background
-
return EXIT_SUCCESS on success, 1 on failure
+
Merge the current image with a checkerboard background
+
Returns EXIT_SUCCESS on success, EXIT_FAILURE on failure
*/
int merge_with_background(RImage *i)
{
@@ -302,10 +1100,10 @@ int merge_with_background(RImage *i)
}
/*
-
turn_image: rotate the image by the angle passed
-
return EXIT_SUCCESS on success, EXIT_FAILURE on failure
+
Rotate the image by the angle passed
+
Returns EXIT_SUCCESS on success, EXIT_FAILURE on failure
*/
-int turn_image(float angle)
+int rotate_image(float angle)
{
RImage *tmp;
@@ -337,27 +1135,26 @@ int turn_image(float angle)
}
/*
-
turn_image_right: rotate the image by 90 degree
-
return EXIT_SUCCESS on success, EXIT_FAILURE on failure
+
Rotate the image by 90 degrees to the right
+
Returns EXIT_SUCCESS on success, EXIT_FAILURE on failure
*/
-int turn_image_right(void)
+int rotate_image_right(void)
{
-
return turn_image(90.0);
+
return rotate_image(90.0);
}
/*
-
turn_image_left: rotate the image by -90 degree
-
return EXIT_SUCCESS on success, 1 on failure
+
Rotate the image by -90 degrees to the left
+
Returns EXIT_SUCCESS on success, EXIT_FAILURE on failure
*/
-int turn_image_left(void)
+int rotate_image_left(void)
{
-
return turn_image(-90.0);
+
return rotate_image(-90.0);
}
/*
-
draw_failed_image: create a red crossed image to indicate an error loading file
-
return the image on success, NULL on failure
-
+
Create a red crossed image to indicate an error loading file
+
Returns the image on success, NULL on failure
*/
RImage *draw_failed_image(void)
{
@@ -372,53 +1169,176 @@ RImage *draw_failed_image(void)
return NULL;
RFillImage(failed_image, &black);
-
ROperateLine(failed_image, RAddOperation, 0, 0, failed_image->width, failed_image->height, &red);
-
ROperateLine(failed_image, RAddOperation, 0, failed_image->height, failed_image->width, 0, &red);
+
+
/* Calculate 80% size cross centered in the image */
+
int cross_width = (int)(failed_image->width * 0.6);
+
int cross_height = (int)(failed_image->height * 0.6);
+
int offset_x = (failed_image->width - cross_width) / 2;
+
int offset_y = (failed_image->height - cross_height) / 2;
+
+
/* Calculate line thickness based on image size (minimum 3, maximum 8 pixels) */
+
int thickness = (failed_image->width + failed_image->height) / 100;
+
if (thickness < 3) thickness = 3;
+
if (thickness > 8) thickness = 8;
+
+
/* Draw thick diagonal lines for the cross using 80% of the image size */
+
for (int i = -thickness/2; i <= thickness/2; i++) {
+
/* First diagonal (top-left to bottom-right) */
+
ROperateLine(failed_image, RAddOperation,
+
offset_x + i, offset_y,
+
offset_x + cross_width + i, offset_y + cross_height, &red);
+
ROperateLine(failed_image, RAddOperation,
+
offset_x, offset_y + i,
+
offset_x + cross_width, offset_y + cross_height + i, &red);
+
+
/* Second diagonal (top-right to bottom-left) */
+
ROperateLine(failed_image, RAddOperation,
+
offset_x + i, offset_y + cross_height,
+
offset_x + cross_width + i, offset_y, &red);
+
ROperateLine(failed_image, RAddOperation,
+
offset_x, offset_y + cross_height - i,
+
offset_x + cross_width, offset_y - i, &red);
+
}
return failed_image;
}
/*
-
full_screen: sending event to the window manager to switch from/to full screen mode
-
return EXIT_SUCCESS on success, 1 on failure
+
Send event to the window manager to switch from/to full screen mode
+
Returns EXIT_SUCCESS on success, EXIT_FAILURE on failure
+*/
+int full_screen(void)
+{
+
XEvent xev;
+
+
Atom wm_state = XInternAtom(dpy, "_NET_WM_STATE", True);
+
Atom fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", True);
+
long mask = SubstructureNotifyMask;
+
+
if (fullscreen_flag) {
+
fullscreen_flag = False;
+
zoom_factor = 0;
+
back_from_fullscreen = True;
+
} else {
+
fullscreen_flag = True;
+
zoom_factor = 1000;
+
RReleaseImage(img);
+
img = load_oriented_image(ctx, current_link->data, 0);
+
if (!img)
+
img = draw_failed_image();
+
else
+
merge_with_background(img);
+
}
+
+
memset(&xev, 0, sizeof(xev));
+
xev.type = ClientMessage;
+
xev.xclient.display = dpy;
+
xev.xclient.window = win;
+
xev.xclient.message_type = wm_state;
+
xev.xclient.format = 32;
+
xev.xclient.data.l[0] = fullscreen_flag;
+
xev.xclient.data.l[1] = fullscreen;
+
+
if (!XSendEvent(dpy, DefaultRootWindow(dpy), False, mask, &xev)) {
+
fprintf(stderr, "Error: sending fullscreen event to xserver\n");
+
return EXIT_FAILURE;
+
}
+
return EXIT_SUCCESS;
+}
+
+/*
+
Init the linked list
+*/
+void linked_list_init(linked_list_t *list)
+{
+
list->first = list->last = 0;
+
list->count = 0;
+}
+
+/*
+
Add an element to the linked list
+
Returns EXIT_SUCCESS on success, EXIT_FAILURE otherwise
+*/
+int linked_list_add(linked_list_t *list, const void *data)
+{
+
link_t *link;
+
+
/* calloc sets the "next" field to zero. */
+
link = calloc(1, sizeof(link_t));
+
if (!link) {
+
fprintf(stderr, "Error: failed to allocate memory\n");
+
return EXIT_FAILURE;
+
}
+
link->data = data;
+
if (list->last) {
+
/* Join the two final links together. */
+
list->last->next = link;
+
link->prev = list->last;
+
list->last = link;
+
} else {
+
list->first = link;
+
list->last = link;
+
}
+
list->count++;
+
return EXIT_SUCCESS;
+}
+
+/*
+
Delete an element from the linked list
+
Returns EXIT_SUCCESS on success, EXIT_FAILURE otherwise
*/
-int full_screen(void)
+int linked_list_del(linked_list_t *list, link_t *link)
{
-
XEvent xev;
+
if (!list || !link) {
+
return EXIT_FAILURE;
+
}
-
Atom wm_state = XInternAtom(dpy, "_NET_WM_STATE", True);
-
Atom fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", True);
-
long mask = SubstructureNotifyMask;
+
/* Update previous link's next pointer */
+
if (link->prev) {
+
link->prev->next = link->next;
+
} else {
+
/* This was the first link */
+
list->first = link->next;
+
}
-
if (fullscreen_flag) {
-
fullscreen_flag = False;
-
zoom_factor = 0;
-
back_from_fullscreen = True;
+
/* Update next link's previous pointer */
+
if (link->next) {
+
link->next->prev = link->prev;
} else {
-
fullscreen_flag = True;
-
zoom_factor = 1000;
+
/* This was the last link */
+
list->last = link->prev;
}
-
memset(&xev, 0, sizeof(xev));
-
xev.type = ClientMessage;
-
xev.xclient.display = dpy;
-
xev.xclient.window = win;
-
xev.xclient.message_type = wm_state;
-
xev.xclient.format = 32;
-
xev.xclient.data.l[0] = fullscreen_flag;
-
xev.xclient.data.l[1] = fullscreen;
+
if (link->data)
+
free((char *)link->data);
+
free(link);
+
+
list->count--;
-
if (!XSendEvent(dpy, DefaultRootWindow(dpy), False, mask, &xev)) {
-
fprintf(stderr, "Error: sending fullscreen event to xserver\n");
-
return EXIT_FAILURE;
-
}
return EXIT_SUCCESS;
}
/*
-
zoom_in_out: apply a zoom factor on the current image
-
arg: 1 to zoom in, 0 to zoom out
-
return EXIT_SUCCESS on success, 1 on failure
+
Deallocate the whole linked list
+*/
+void linked_list_free(linked_list_t *list)
+{
+
link_t *link;
+
link_t *next;
+
for (link = list->first; link; link = next) {
+
/* Store the next value so that we don't access freed memory. */
+
next = link->next;
+
if (link->data)
+
free((char *)link->data);
+
free(link);
+
}
+
current_link = NULL;
+}
+
+/*
+
Apply a zoom factor on the current image
+
Arg: 1 to zoom in, 0 to zoom out
+
Returns EXIT_SUCCESS on success, EXIT_FAILURE on failure
*/
int zoom_in_out(int z)
{
@@ -458,7 +1378,7 @@ int zoom_in_out(int z)
merge_with_background(img);
if (!RConvertImage(ctx, img, &pix)) {
-
fprintf(stderr, "%s\n", RMessageForError(RErrorCode));
+
fprintf(stderr, "Error: %s\n", RMessageForError(RErrorCode));
return EXIT_FAILURE;
}
XResizeWindow(dpy, win, img->width, img->height);
@@ -466,8 +1386,8 @@ int zoom_in_out(int z)
}
/*
-
zoom_in: transitional fct used to call zoom_in_out with zoom in flag
-
return EXIT_SUCCESS on success, 1 on failure
+
Transitional function used to call zoom_in_out with zoom in flag
+
Returns EXIT_SUCCESS on success, EXIT_FAILURE on failure
*/
int zoom_in(void)
{
@@ -475,8 +1395,8 @@ int zoom_in(void)
}
/*
-
zoom_out: transitional fct used to call zoom_in_out with zoom out flag
-
return EXIT_SUCCESS on success, 1 on failure
+
Transitional function used to call zoom_in_out with zoom out flag
+
Returns EXIT_SUCCESS on success, EXIT_FAILURE on failure
*/
int zoom_out(void)
{
@@ -484,12 +1404,15 @@ int zoom_out(void)
}
/*
-
change_image: load previous or next image
-
arg: way which could be PREV or NEXT constant
-
return EXIT_SUCCESS on success, 1 on failure
+
Load previous or next image
+
Arg: way which could be PREV or NEXT constant
+
Returns EXIT_SUCCESS on success, EXIT_FAILURE on failure
*/
int change_image(int way)
{
+
if (max_index == 0)
+
return EXIT_FAILURE;
+
if (img && current_link) {
int old_img_width = img->width;
int old_img_height = img->height;
@@ -513,27 +1436,49 @@ int change_image(int way)
}
}
if (WMIV_DEBUG)
-
fprintf(stderr, "current file is> %s\n", (char *)current_link->data);
+
fprintf(stderr, "Current file is> %s\n", (char *)current_link->data);
img = load_oriented_image(ctx, current_link->data, 0);
-
if (!img) {
-
fprintf(stderr, "Error: %s %s\n", (char *)current_link->data,
-
RMessageForError(RErrorCode));
+
if (strlen((char *)current_link->data) == 0)
+
fprintf(stderr, "Error: %s\n", RMessageForError(RErrorCode));
+
else
+
fprintf(stderr, "Error: %s %s\n", (char *)current_link->data, RMessageForError(RErrorCode));
img = draw_failed_image();
+
if (ignore_unknown_file_format) {
+
link_t *tmp_link;
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Skipping file...\n");
+
if (way == NEXT)
+
if (!current_link->prev)
+
tmp_link = list.last;
+
else
+
tmp_link = current_link->prev;
+
else
+
if (!current_link->next)
+
tmp_link = list.first;
+
else
+
tmp_link = current_link->next;
+
linked_list_del(&list, current_link);
+
current_link = tmp_link;
+
max_index = list.count;
+
return change_image(way);
+
}
} else {
merge_with_background(img);
}
rescale_image();
-
if (!fullscreen_flag) {
-
if ((old_img_width != img->width) || (old_img_height != img->height))
-
XResizeWindow(dpy, win, img->width, img->height);
-
else
-
XCopyArea(dpy, pix, win, ctx->copy_gc, 0, 0, img->width, img->height, 0, 0);
-
change_title(&title_property, (char *)current_link->data);
-
} else {
-
XClearWindow(dpy, win);
-
XCopyArea(dpy, pix, win, ctx->copy_gc, 0, 0,
-
img->width, img->height, max_width/2-img->width/2, max_height/2-img->height/2);
+
if (win) {
+
if (!fullscreen_flag) {
+
if ((old_img_width != img->width) || (old_img_height != img->height))
+
XResizeWindow(dpy, win, img->width, img->height);
+
else
+
XCopyArea(dpy, pix, win, ctx->copy_gc, 0, 0, img->width, img->height, 0, 0);
+
change_title(&title_property, (char *)current_link->data);
+
} else {
+
XClearWindow(dpy, win);
+
XCopyArea(dpy, pix, win, ctx->copy_gc, 0, 0,
+
img->width, img->height, max_width/2-img->width/2, max_height/2-img->height/2);
+
}
}
return EXIT_SUCCESS;
}
@@ -542,11 +1487,9 @@ int change_image(int way)
#ifdef HAVE_PTHREAD
/*
-
diaporama: send a xevent to display the next image at every delay set to diaporama_delay
-
arg: not used
-
return void
+
Send an X event to display the next image at every delay set to slideshow_delay
*/
-void *diaporama(void *arg)
+void *slideshow(void *arg)
{
(void) arg;
@@ -565,14 +1508,14 @@ void *diaporama(void *arg)
event.state = 0;
event.type = KeyPress;
-
while (diaporama_flag) {
+
while (slideshow_flag) {
int r;
r = XSendEvent(event.display, event.window, True, KeyPressMask, (XEvent *)&event);
if (!r)
-
fprintf(stderr, "Error sending event\n");
+
fprintf(stderr, "Error: can't send event\n");
XFlush(dpy);
/* default sleep time between moving to next image */
-
sleep(diaporama_delay);
+
sleep(slideshow_delay);
}
tid = 0;
return arg;
@@ -580,64 +1523,11 @@ void *diaporama(void *arg)
#endif
/*
-
linked_list_init: init the linked list
-*/
-void linked_list_init(linked_list_t *list)
-{
-
list->first = list->last = 0;
-
list->count = 0;
-}
-
-/*
-
linked_list_add: add an element to the linked list
-
return EXIT_SUCCESS on success, 1 otherwise
-*/
-int linked_list_add(linked_list_t *list, const void *data)
-{
-
link_t *link;
-
-
/* calloc sets the "next" field to zero. */
-
link = calloc(1, sizeof(link_t));
-
if (!link) {
-
fprintf(stderr, "Error: memory allocation failed\n");
-
return EXIT_FAILURE;
-
}
-
link->data = data;
-
if (list->last) {
-
/* Join the two final links together. */
-
list->last->next = link;
-
link->prev = list->last;
-
list->last = link;
-
} else {
-
list->first = link;
-
list->last = link;
-
}
-
list->count++;
-
return EXIT_SUCCESS;
-}
-
-/*
-
linked_list_free: deallocate the whole linked list
-*/
-void linked_list_free(linked_list_t *list)
-{
-
link_t *link;
-
link_t *next;
-
for (link = list->first; link; link = next) {
-
/* Store the next value so that we don't access freed memory. */
-
next = link->next;
-
if (link->data)
-
free((char *)link->data);
-
free(link);
-
}
-}
-
-/*
-
connect_dir: list and sort by name all files from a given directory
-
arg: the directory path that contains images, the linked list where to add the new file refs
-
return: the first argument of the list or NULL on failure
+
List and sort by name all files from a given directory
+
Arg: the directory path that contains images, the linked list where to add the new file refs
+
Returns: the first argument of the list or NULL on failure
*/
-link_t *connect_dir(char *dirpath, linked_list_t *li)
+link_t *connect_dir(char *dirpath, linked_list_t *list)
{
struct dirent **dir;
int dv, idx;
@@ -650,12 +1540,9 @@ link_t *connect_dir(char *dirpath, linked_list_t *li)
if (dv < 0) {
/* maybe it's a file */
struct stat stDirInfo;
-
if (lstat(dirpath, &stDirInfo) == 0) {
-
linked_list_add(li, strdup(dirpath));
-
return li->first;
-
} else {
-
return NULL;
-
}
+
if (lstat(dirpath, &stDirInfo) == 0)
+
linked_list_add(list, strdup(dirpath));
+
return list->first;
}
for (idx = 0; idx < dv; idx++) {
struct stat stDirInfo;
@@ -666,10 +1553,147 @@ link_t *connect_dir(char *dirpath, linked_list_t *li)
free(dir[idx]);
if ((lstat(path, &stDirInfo) == 0) && !S_ISDIR(stDirInfo.st_mode))
-
linked_list_add(li, strdup(path));
+
linked_list_add(list, strdup(path));
}
free(dir);
-
return li->first;
+
return list->first;
+}
+
+#ifdef USE_RANDR
+/*
+
Retrieve the monitor resolution where the window is located
+*/
+void get_monitor_dimensions(Display *dpy, int screen_num, Window win, int *width, int *height)
+{
+
int monitor_count, abs_x, abs_y;
+
Window child;
+
Window root_window = RootWindow(dpy, screen_num);
+
XWindowAttributes attrs;
+
XGetWindowAttributes(dpy, win, &attrs);
+
XTranslateCoordinates(dpy, win, attrs.root, 0, 0, &abs_x, &abs_y, &child);
+
+
XRRMonitorInfo *monitors = XRRGetMonitors(dpy, root_window, True, &monitor_count);
+
if (monitors && monitor_count > 0) {
+
for (int i = 0; i < monitor_count; i++) {
+
if (abs_x >= monitors[i].x && abs_x < (monitors[i].x + monitors[i].width) &&
+
abs_y >= monitors[i].y && abs_y < (monitors[i].y + monitors[i].height)) {
+
*width = monitors[i].width;
+
*height = monitors[i].height;
+
break;
+
}
+
}
+
XRRFreeMonitors(monitors);
+
}
+}
+#endif
+
+/*
+
Transform the given URI to UTF-8 string
+*/
+void decode_uri(char *uri)
+{
+
char *last = uri + strlen(uri);
+
+
while (uri < last-2) {
+
if (*uri == '%') {
+
int h;
+
if (sscanf(uri+1, "%2X", &h) != 1)
+
break;
+
*uri = h;
+
memmove(uri+1, uri+3, last - (uri+2));
+
last -= 2;
+
}
+
uri++;
+
}
+}
+
+/*
+
Provide data for drag operations
+*/
+static void widget_get_data_callback(DndClass *dnd, Window window, unsigned char **data, int *length, Atom type) {
+
/* Mark unused parameters to suppress compiler warnings */
+
(void)dnd;
+
(void)window;
+
(void)type;
+
char *filename, *full_path, *uri_data;
+
int uri_length = 0;
+
static char *last_provided_data = NULL;
+
+
if (!current_link || !current_link->data) {
+
*data = NULL;
+
*length = 0;
+
return;
+
}
+
+
/* Get the full absolute path for the current file */
+
filename = (char *)current_link->data;
+
full_path = realpath(filename, NULL);
+
+
if (!full_path) {
+
*data = NULL;
+
*length = 0;
+
return;
+
}
+
+
/* Create file:// URI for the current file */
+
uri_length = strlen("file://") + strlen(full_path) + 1;
+
uri_data = malloc(uri_length);
+
if (!uri_data) {
+
free(full_path);
+
*data = NULL;
+
*length = 0;
+
return;
+
}
+
+
snprintf(uri_data, uri_length, "file://%s", full_path);
+
*data = (unsigned char *)uri_data;
+
*length = strlen(uri_data);
+
+
/* Only log if this is different data than last time */
+
if (WMIV_DEBUG && (!last_provided_data || strcmp(uri_data, last_provided_data) != 0)) {
+
fprintf(stderr, "Providing drag data: %s\n", uri_data);
+
if (last_provided_data)
+
free(last_provided_data);
+
last_provided_data = strdup(uri_data);
+
}
+
free(full_path);
+}
+
+/*
+
Initialize DND for sending
+*/
+static void init_dnd_send(void) {
+
if (!dnd_initialized) {
+
Atom typelist[] = {
+
XInternAtom(dpy, "text/uri-list", False),
+
XInternAtom(dpy, "text/plain", False),
+
None
+
};
+
+
xdnd_init(&send_dnd, dpy);
+
+
send_dnd.widget_get_data = widget_get_data_callback;
+
dnd_initialized = True;
+
xdnd_set_dnd_aware(&send_dnd, win, typelist);
+
}
+}
+
+/*
+
Initiate a file drag operation
+*/
+static void initiate_file_drag(void) {
+
Atom action = XInternAtom(dpy, "XdndActionCopy", False);
+
Atom typelist[] = {
+
XInternAtom(dpy, "text/uri-list", False),
+
None
+
};
+
+
if (!current_link || !current_link->data) {
+
return;
+
}
+
+
init_dnd_send();
+
xdnd_drag(&send_dnd, win, action, typelist);
}
/*
@@ -682,23 +1706,28 @@ int main(int argc, char **argv)
XEvent e;
KeySym keysym;
char *reading_filename = "";
-
int screen, file_i;
+
int screen_num, file_i;
int quit = 0;
XClassHint *class_hints;
XSizeHints *size_hints;
XWMHints *win_hints;
+
Atom delWindow;
+
Atom xdnd_aware;
+
Atom xdnd_version = XDND_VERSION;
+
#ifdef USE_XPM
Pixmap icon_pixmap, icon_shape;
#endif
+
class_hints = XAllocClassHint();
if (!class_hints) {
-
fprintf(stderr, "Error: failure allocating memory\n");
+
fprintf(stderr, "Error: failed to allocate memory\n");
return EXIT_FAILURE;
}
class_hints->res_name = (char *)APPNAME;
-
class_hints->res_class = "default";
+
class_hints->res_class = "wmiv";
-
/* init colors */
+
/* Init colors */
lightGray.red = lightGray.green = lightGray.blue = 211;
darkGray.red = darkGray.green = darkGray.blue = 169;
lightGray.alpha = darkGray.alpha = 1;
@@ -707,39 +1736,46 @@ int main(int argc, char **argv)
red.green = red.blue = 0;
static struct option long_options[] = {
-
{"version", no_argument, 0, 'v'},
-
{"help", no_argument, 0, 'h'},
-
{0, 0, 0, 0}
-
};
+
{"version", no_argument, 0, 'v'},
+
{"help", no_argument, 0, 'h'},
+
{"ignore-unknown", no_argument, 0, 'i'},
+
{0, 0, 0, 0}
+
};
int option_index = 0;
-
option = getopt_long (argc, argv, "hv", long_options, &option_index);
+
option = getopt_long (argc, argv, "hiv", long_options, &option_index);
if (option != -1) {
switch (option) {
case 'h':
-
printf("Usage: %s [image(s)|directory]\n"
+
printf("Usage: %s [image(s)|directory|archive]\n"
"Options:\n"
-
" -h, --help print this help text\n"
-
" -v, --version print version\n"
-
"Keys:\n"
+
" -h, --help print this help text\n"
+
" -v, --version print version\n"
+
" -i, --ignore-unknown ignore unknown image format\n"
+
"\nKeys:\n\n"
" [+] zoom in\n"
" [-] zoom out\n"
-
" [Esc] actual size\n"
+
" [▸] next image\n"
+
" [◂] previous image\n"
+
" [▴] first image\n"
+
" [▾] last image\n"
+
" [Ctrl+C] copy image to clipboard\n"
#ifdef HAVE_PTHREAD
-
" [D] launch diaporama mode\n"
+
" [D] start slideshow\n"
#endif
+
" [Esc] actual size\n"
+
" [F] toggle full-screen mode\n"
" [L] rotate image on the left\n"
" [Q] quit\n"
-
" [R] rotate image on the right\n"
-
" [▸] next image\n"
-
" [◂] previous image\n"
-
" [▴] first image\n"
-
" [▾] last image\n",
+
" [R] rotate image on the right\n",
argv[0]);
return EXIT_SUCCESS;
case 'v':
printf("%s version %s\n", APPNAME, VERSION);
return EXIT_SUCCESS;
+
case 'i':
+
ignore_unknown_file_format = True;
+
break;
case '?':
return EXIT_FAILURE;
}
@@ -747,6 +1783,10 @@ int main(int argc, char **argv)
linked_list_init(&list);
+#ifdef HAVE_LIBARCHIVE
+
/* Register cleanup function for temporary directory */
+
atexit(cleanup_temp_dir);
+#endif
dpy = XOpenDisplay(NULL);
if (!dpy) {
fprintf(stderr, "Error: can't open display\n");
@@ -754,14 +1794,22 @@ int main(int argc, char **argv)
return EXIT_FAILURE;
}
-
screen = DefaultScreen(dpy);
-
max_width = DisplayWidth(dpy, screen);
-
max_height = DisplayHeight(dpy, screen);
+
/* Initialize clipboard atoms */
+
clipboard_atom = XInternAtom(dpy, "CLIPBOARD", False);
+
targets_atom = XInternAtom(dpy, "TARGETS", False);
+
png_atom = XInternAtom(dpy, "image/png", False);
+
jpeg_atom = XInternAtom(dpy, "image/jpeg", False);
+
property_atom = XInternAtom(dpy, "WMIV_CLIPBOARD_PROP", False);
+
incr_atom = XInternAtom(dpy, "INCR", False);
+
+
screen_num = DefaultScreen(dpy);
+
max_width = DisplayWidth(dpy, screen_num);
+
max_height = DisplayHeight(dpy, screen_num);
attr.flags = RC_RenderMode | RC_ColorsPerChannel;
attr.render_mode = RDitheredRendering;
attr.colors_per_channel = 4;
-
ctx = RCreateContext(dpy, DefaultScreen(dpy), &attr);
+
ctx = RCreateContext(dpy, screen_num, &attr);
if (argc < 2) {
argv[1] = ".";
@@ -769,55 +1817,122 @@ int main(int argc, char **argv)
}
for (file_i = 1; file_i < argc; file_i++) {
-
current_link = connect_dir(argv[file_i], &list);
-
if (current_link) {
-
reading_filename = (char *)current_link->data;
-
max_index = list.count;
+
/* Check if this is an archive file */
+
if (is_archive_file(argv[file_i])) {
+
char *extracted_dir = extract_archive(argv[file_i]);
+
if (extracted_dir) {
+
current_link = connect_dir(extracted_dir, &list);
+
free(extracted_dir);
+
if (current_link) {
+
reading_filename = (char *)current_link->data;
+
max_index = list.count;
+
}
+
}
+
} else {
+
current_link = connect_dir(argv[file_i], &list);
+
if (current_link) {
+
reading_filename = (char *)current_link->data;
+
max_index = list.count;
+
}
}
}
-
img = load_oriented_image(ctx, reading_filename, 0);
if (!img) {
-
fprintf(stderr, "Error: %s %s\n", reading_filename, RMessageForError(RErrorCode));
+
if (strlen(reading_filename) == 0)
+
fprintf(stderr, "Error: %s\n", RMessageForError(RErrorCode));
+
else
+
fprintf(stderr, "Error: %s %s\n", reading_filename, RMessageForError(RErrorCode));
img = draw_failed_image();
if (!current_link)
return EXIT_FAILURE;
+
if (ignore_unknown_file_format) {
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Skipping file...\n");
+
if (change_image(NEXT) != EXIT_SUCCESS || !img)
+
return EXIT_FAILURE;
+
if (current_link)
+
reading_filename = (char *)current_link->data;
+
}
}
merge_with_background(img);
-
rescale_image();
-
if (WMIV_DEBUG)
-
fprintf(stderr, "display size: %dx%d\n", max_width, max_height);
-
-
win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0,
-
img->width, img->height, 0, 0, BlackPixel(dpy, screen));
-
XSelectInput(dpy, win, KeyPressMask|StructureNotifyMask|ExposureMask|ButtonPressMask|FocusChangeMask);
+
win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, 1, 1, 0, 0, BlackPixel(dpy, screen_num));
+
XSelectInput(dpy, win, KeyPressMask|StructureNotifyMask|ExposureMask|ButtonPressMask|ButtonReleaseMask|PointerMotionMask|FocusChangeMask|PropertyChangeMask);
size_hints = XAllocSizeHints();
if (!size_hints) {
-
fprintf(stderr, "Error: failure allocating memory\n");
+
fprintf(stderr, "Error: failed to allocate memory\n");
return EXIT_FAILURE;
}
size_hints->width = img->width;
size_hints->height = img->height;
-
Atom delWindow = XInternAtom(dpy, "WM_DELETE_WINDOW", 0);
+
delWindow = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
XSetWMProtocols(dpy, win, &delWindow, 1);
change_title(&title_property, reading_filename);
+
xdnd_aware = XInternAtom(dpy, "XdndAware", False);
+
XChangeProperty(dpy, win, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *) &xdnd_version, 1);
win_hints = XAllocWMHints();
+
if (win_hints) {
win_hints->flags = StateHint|InputHint|WindowGroupHint;
#ifdef USE_XPM
if ((XpmCreatePixmapFromData(dpy, win, wmiv_xpm, &icon_pixmap, &icon_shape, NULL)) == 0) {
-
win_hints->flags |= IconPixmapHint|IconMaskHint|IconPositionHint;
+
win_hints->flags |= IconPixmapHint|IconMaskHint;
win_hints->icon_pixmap = icon_pixmap;
win_hints->icon_mask = icon_shape;
-
win_hints->icon_x = 0;
-
win_hints->icon_y = 0;
+
+
Atom net_wm_icon = XInternAtom(dpy, "_NET_WM_ICON", False);
+
if (net_wm_icon != None) {
+
int icon_width, icon_height;
+
if (sscanf(wmiv_xpm[0], "%d %d", &icon_width, &icon_height) == 2) {
+
/* Convert XPM pixmap to ARGB32 format */
+
XImage *icon_image = XGetImage(dpy, icon_pixmap, 0, 0, icon_width, icon_height, AllPlanes, ZPixmap);
+
if (icon_image) {
+
XImage *mask_image = NULL;
+
if (icon_shape != None) {
+
mask_image = XGetImage(dpy, icon_shape, 0, 0, icon_width, icon_height, AllPlanes, ZPixmap);
+
}
+
+
/* Create ARGB32 data: width, height, followed by ARGB pixel data */
+
unsigned long *icon_data = malloc(sizeof(unsigned long) * (2 + icon_width * icon_height));
+
if (icon_data) {
+
icon_data[0] = icon_width;
+
icon_data[1] = icon_height;
+
+
for (int y = 0; y < icon_height; y++) {
+
for (int x = 0; x < icon_width; x++) {
+
unsigned long pixel = XGetPixel(icon_image, x, y);
+
unsigned long alpha = 0xFF000000; /* default opaque */
+
+
/* Check mask for transparency */
+
if (mask_image) {
+
unsigned long mask_pixel = XGetPixel(mask_image, x, y);
+
if (mask_pixel == 0) {
+
alpha = 0x00000000; /* transparent */
+
}
+
}
+
+
icon_data[2 + y * icon_width + x] = alpha | (pixel & 0x00FFFFFF);
+
}
+
}
+
+
XChangeProperty(dpy, win, net_wm_icon, XA_CARDINAL, 32, PropModeReplace,
+
(unsigned char *)icon_data, 2 + icon_width * icon_height);
+
free(icon_data);
+
}
+
+
XDestroyImage(icon_image);
+
if (mask_image) {
+
XDestroyImage(mask_image);
+
}
+
}
+
}
+
}
}
#endif
win_hints->initial_state = NormalState;
@@ -830,25 +1945,131 @@ int main(int argc, char **argv)
XFree(win_hints);
XFree(class_hints);
XFree(size_hints);
-
}
XMapWindow(dpy, win);
XFlush(dpy);
+
+
if (wait_for_frame_extents(dpy, win, 1000)) { /* Wait up to 1 second */
+
if (!get_window_decor_size(dpy, win, &extents) && WMIV_DEBUG)
+
fprintf(stderr, "Warning: Property found but could not parse frame extents\n");
+
} else {
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Warning: Window manager did not set _NET_FRAME_EXTENTS property within timeout\n");
+
}
+
+#ifdef USE_RANDR
+
get_monitor_dimensions(dpy, screen_num, win, &max_width, &max_height);
+#endif
+
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Display size: %dx%d\n", max_width, max_height);
+
+
rescale_image();
XCopyArea(dpy, pix, win, ctx->copy_gc, 0, 0, img->width, img->height, 0, 0);
+
XResizeWindow(dpy, win, img->width, img->height);
+
XSync(dpy, True);
+
/* Main event loop */
while (!quit) {
XNextEvent(dpy, &e);
if (e.type == ClientMessage) {
+
unsigned char *dropBuffer;
+
Atom typelist[] = {
+
XInternAtom(dpy, "text/uri-list", False),
+
XInternAtom(dpy, "text/plain", False),
+
None
+
};
+
Atom actionlist[] = {
+
XInternAtom(dpy, "XdndActionCopy", False),
+
XInternAtom(dpy, "XdndActionMove", False),
+
None
+
};
+
int length;
+
Atom type;
+
int x, y;
+
+
if (xdnd_get_drop(dpy, &e, typelist, actionlist, &dropBuffer, &length, &type, &x, &y)) {
+
char *handled_uri_header = "file://";
+
char *filename_separator = "\n";
+
char *ptr = strtok((char *)dropBuffer, filename_separator);
+
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Dropped data: %s\n", (char *)dropBuffer);
+
+
linked_list_free(&list);
+
linked_list_init(&list);
+
+
Bool find_one_file = False;
+
while (ptr != NULL) {
+
if (strstr(ptr, handled_uri_header) == ptr) {
+
char *tmp_file;
+
if (ptr[strlen(ptr) - 1] == '\r')
+
ptr[strlen(ptr) - 1] = '\0';
+
tmp_file = ptr + strlen(handled_uri_header);
+
+
/* drag-and-drop file name format as per the spec is encoded as an URI */
+
decode_uri(tmp_file);
+
if (connect_dir(tmp_file, &list))
+
find_one_file = True;
+
}
+
ptr = strtok(NULL, filename_separator);
+
}
+
free(dropBuffer);
+
+
if (find_one_file) {
+
max_index = list.count;
+
current_link = list.last;
+
change_image(NEXT);
+
} else {
+
if (WMIV_DEBUG)
+
fprintf(stderr, "Error: no valid file found in dropped data\n");
+
XClearArea(dpy, win, 0, 0, 0, 0, True);
+
}
+
}
if (e.xclient.data.l[0] == delWindow)
quit = 1;
-
-
/*
-
* This break could be related to all ClientMessages or
-
* related to delWindow. Before the patch about this comment
-
* the break was indented with one tab more (at the same level
-
* than "quit = 1;" in the previous line.
-
*/
-
break;
+
continue;
+
}
+
if (e.type == SelectionRequest) {
+
/* Serve clipboard requests */
+
handle_selection_request(&e.xselectionrequest);
+
continue;
+
}
+
if (e.type == SelectionClear) {
+
/* we lost ownership */
+
handle_selection_clear();
+
continue;
+
}
+
if (e.type == PropertyNotify) {
+
XPropertyEvent *pe = &e.xproperty;
+
if (incr.active &&
+
(pe->window == incr.requestor) &&
+
(pe->atom == incr.property) &&
+
(pe->state == PropertyDelete)) {
+
+
if (WMIV_DEBUG)
+
fprintf(stderr, "INCR: property delete observed, sending next chunk\n");
+
+
/* Check if we've already sent all data */
+
if (incr.offset >= incr.total_size) {
+
/* Send zero-length termination to signal end of transfer */
+
XChangeProperty(dpy, incr.requestor, incr.property, incr.target, 8, PropModeReplace, NULL, 0);
+
XFlush(dpy);
+
/* Remove PropertyChangeMask from requestor window to clean up */
+
XSelectInput(dpy, incr.requestor, NoEventMask);
+
incr.active = False;
+
if (WMIV_DEBUG)
+
fprintf(stderr, "INCR: transfer complete\n");
+
} else {
+
/* Send next data chunk */
+
send_next_incr_chunk();
+
}
+
} else if (pe->window == win) {
+
if (WMIV_DEBUG && incr.active) {
+
fprintf(stderr, "Ignoring PropertyNotify on main window during INCR\n");
+
}
+
}
+
continue;
}
if (e.type == FocusIn) {
focus = True;
@@ -866,6 +2087,13 @@ int main(int argc, char **argv)
}
if (!fullscreen_flag && e.type == ConfigureNotify) {
XConfigureEvent xce = e.xconfigure;
+
+
/* there is no file loaded and the window is resized */
+
if (!current_link) {
+
XResizeWindow(dpy, win, img->width, img->height);
+
continue;
+
}
+
if (xce.width != img->width || xce.height != img->height) {
RImage *old_img = img;
img = load_oriented_image(ctx, current_link->data, 0);
@@ -875,9 +2103,10 @@ int main(int argc, char **argv)
XResizeWindow(dpy, win, img->width, img->height);
} else {
RImage *tmp2;
-
if (!back_from_fullscreen)
+
if (!back_from_fullscreen) {
/* manually resized window */
tmp2 = RScaleImage(img, xce.width, xce.height);
+
}
else {
/* back from fullscreen mode, maybe img was rotated */
tmp2 = img;
@@ -891,9 +2120,10 @@ int main(int argc, char **argv)
RReleaseImage(tmp2);
change_title(&title_property, (char *)current_link->data);
XSync(dpy, True);
+
rescale_image();
XResizeWindow(dpy, win, img->width, img->height);
XCopyArea(dpy, pix, win, ctx->copy_gc, 0, 0,
-
img->width, img->height, 0, 0);
+
img->width, img->height, 0, 0);
}
}
@@ -901,20 +2131,21 @@ int main(int argc, char **argv)
continue;
}
if (fullscreen_flag && e.type == ConfigureNotify) {
-
maximize_image();
+
int new_width = e.xconfigure.width;
+
int new_height = e.xconfigure.height;
+
if (new_width != img->width || new_height != img->height)
+
maximize_image();
continue;
}
if (e.type == ButtonPress) {
switch (e.xbutton.button) {
case Button1: {
-
if (focus) {
-
if (img && (e.xbutton.x > img->width/2))
-
change_image(NEXT);
-
else
-
change_image(PREV);
-
}
-
}
-
break;
+
/* Store button press for potential drag operation */
+
button1_pressed = True;
+
drag_start_x = e.xbutton.x;
+
drag_start_y = e.xbutton.y;
+
}
+
break;
case Button4:
zoom_in();
break;
@@ -930,12 +2161,44 @@ int main(int argc, char **argv)
}
continue;
}
+
if (e.type == ButtonRelease) {
+
if (e.xbutton.button == Button1 && button1_pressed) {
+
/* Button released without dragging - treat as click for image navigation */
+
button1_pressed = False;
+
if (focus) {
+
if (img && (drag_start_x > img->width/2))
+
change_image(NEXT);
+
else
+
change_image(PREV);
+
}
+
}
+
continue;
+
}
+
if (e.type == MotionNotify && button1_pressed) {
+
/* Check if we've moved enough to start a drag */
+
int dx = e.xmotion.x - drag_start_x;
+
int dy = e.xmotion.y - drag_start_y;
+
if (abs(dx) > drag_threshold || abs(dy) > drag_threshold) {
+
/* Start drag operation */
+
button1_pressed = False;
+
if (current_link && current_link->data) {
+
initiate_file_drag();
+
}
+
}
+
continue;
+
}
if (e.type == KeyPress) {
keysym = W_KeycodeToKeysym(dpy, e.xkey.keycode, e.xkey.state & ShiftMask?1:0);
#ifdef HAVE_PTHREAD
if (keysym != XK_Right)
-
diaporama_flag = False;
+
slideshow_flag = False;
#endif
+
/* Detect Ctrl+C: state contains ControlMask */
+
if ((e.xkey.state & ControlMask) && (keysym == XK_c || keysym == XK_C)) {
+
copy_current_image_to_clipboard();
+
continue;
+
}
+
switch (keysym) {
case XK_Right:
change_image(NEXT);
@@ -945,25 +2208,25 @@ int main(int argc, char **argv)
break;
case XK_Up:
if (current_link) {
-
current_link = list.last;
-
change_image(NEXT);
+
current_link = list.last;
+
change_image(NEXT);
}
break;
case XK_Down:
if (current_link) {
-
current_link = list.first;
-
change_image(PREV);
+
current_link = list.first;
+
change_image(PREV);
}
break;
#ifdef HAVE_PTHREAD
case XK_F5:
case XK_d:
if (!tid) {
-
if (current_link && !diaporama_flag) {
-
diaporama_flag = True;
-
pthread_create(&tid, NULL, &diaporama, NULL);
+
if (current_link && !slideshow_flag) {
+
slideshow_flag = True;
+
pthread_create(&tid, NULL, &slideshow, NULL);
} else {
-
fprintf(stderr, "Can't use diaporama mode\n");
+
fprintf(stderr, "Error: can't start slideshow\n");
}
}
break;
@@ -974,10 +2237,8 @@ int main(int argc, char **argv)
case XK_Escape:
if (!fullscreen_flag) {
zoom_factor = -0.2f;
-
/* zoom_in will increase the zoom factor by 0.2 */
zoom_in();
} else {
-
/* we are in fullscreen mode already, want to return to normal size */
full_screen();
}
break;
@@ -992,10 +2253,10 @@ int main(int argc, char **argv)
full_screen();
break;
case XK_r:
-
turn_image_right();
+
rotate_image_right();
break;
case XK_l:
-
turn_image_left();
+
rotate_image_left();
break;
}
@@ -1015,6 +2276,10 @@ int main(int argc, char **argv)
linked_list_free(&list);
RDestroyContext(ctx);
RShutdown();
+
+
free_clipboard_data();
+
cleanup_temp_dir();
+
XCloseDisplay(dpy);
return EXIT_SUCCESS;
}
diff --git a/util/xdnd.c b/util/xdnd.c
new file mode 100644
index 00000000..330db063
--- /dev/null
+++ b/util/xdnd.c
@@ -0,0 +1,1596 @@
+/* xdnd.c, xdnd.h - C program library for handling the Xdnd protocol
+ Copyright (C) 1996-2000 Paul Sheer
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ 02111-1307, USA.
+ */
+
+
+/*
+ Released 1998-08-07
+ Changes:
+
+ 2000-08-08: INCR protocol implemented.
+
+*/
+
+/*
+ DONE:
+ - INCR protocol now implemented
+
+ TODO:
+ - action_choose_dialog not yet supported (never called)
+ - widget_delete_selection not yet supported and DELETE requests are ignored
+ - not yet tested with applications that only supported XDND 0 or 1
+*/
+
+#include <X11/Xlib.h>
+#include <X11/X.h>
+#include <X11/Xatom.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#include "xdnd.h"
+
+static void xdnd_send_enter (DndClass * dnd, Window window, Window from, Atom * typelist);
+static void xdnd_send_position (DndClass * dnd, Window window, Window from, Atom action, int x, int y,
+ unsigned long etime);
+static void xdnd_send_status (DndClass * dnd, Window window, Window from, int will_accept, int want_position,
+ int x, int y, int w, int h, Atom action);
+static void xdnd_send_leave (DndClass * dnd, Window window, Window from);
+static void xdnd_send_drop (DndClass * dnd, Window window, Window from, unsigned long etime);
+static void xdnd_send_finished (DndClass * dnd, Window window, Window from, int error);
+static int xdnd_convert_selection (DndClass * dnd, Window window, Window requester, Atom type);
+static void xdnd_selection_send (DndClass * dnd, XSelectionRequestEvent * request, unsigned char *data,
+ int length);
+static int xdnd_get_selection (DndClass * dnd, Window from, Atom property, Window insert);
+
+
+/* just to remind us : */
+
+#if 0
+typedef struct {
+ int type;
+ unsigned long serial;
+ Bool send_event;
+ Display *display;
+ Window window;
+ Atom message_type;
+ int format;
+ union {
+ char b[20];
+ short s[10];
+ long l[5];
+ } data;
+} XClientMessageEvent;
+XClientMessageEvent xclient;
+#endif
+
+/* #define DND_DEBUG */
+
+#define xdnd_xfree(x) {if (x) { free (x); x = 0; }}
+
+#ifdef DND_DEBUG
+
+#include <sys/time.h>
+#include <unistd.h>
+
+char *xdnd_debug_milliseconds (void)
+{
+ struct timeval tv;
+ static char r[22];
+ gettimeofday (&tv, 0);
+ sprintf (r, "%.2ld.%.3ld", tv.tv_sec % 100L, tv.tv_usec / 1000L);
+ return r;
+}
+
+#define dnd_debug1(a) printf("%s: %d: %s: " a "\n", __FILE__, __LINE__, xdnd_debug_milliseconds ())
+#define dnd_debug2(a,b) printf("%s: %d: %s: " a "\n", __FILE__, __LINE__, xdnd_debug_milliseconds (), b)
+#define dnd_debug3(a,b,c) printf("%s: %d: %s: " a "\n", __FILE__, __LINE__, xdnd_debug_milliseconds (), b, c)
+#define dnd_debug4(a,b,c,d) printf("%s: %d: %s: " a "\n", __FILE__, __LINE__, xdnd_debug_milliseconds (), b, c, d)
+#else
+#define dnd_debug1(a) do {} while (0)
+#define dnd_debug2(a,b) do {} while (0)
+#define dnd_debug3(a,b,c) do {} while (0)
+#define dnd_debug4(a,b,c,d) do {} while (0)
+#endif
+
+#define dnd_warning(a) fprintf (stderr, a)
+
+#define dnd_version_at_least(a,b) ((a) >= (b))
+
+static unsigned char dnd_copy_cursor_bits[] =
+{
+ 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0x00, 0x02, 0x00, 0x08, 0x01,
+ 0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0xe8, 0x0f,
+ 0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0x08, 0x01,
+ 0x02, 0x00, 0x08, 0x00, 0x02, 0x04, 0x08, 0x00, 0x02, 0x0c, 0x08, 0x00,
+ 0x02, 0x1c, 0x08, 0x00, 0x02, 0x3c, 0x08, 0x00, 0x02, 0x7c, 0x08, 0x00,
+ 0x02, 0xfc, 0x08, 0x00, 0x02, 0xfc, 0x09, 0x00, 0x02, 0xfc, 0x0b, 0x00,
+ 0x02, 0x7c, 0x08, 0x00, 0xfe, 0x6d, 0x0f, 0x00, 0x00, 0xc4, 0x00, 0x00,
+ 0x00, 0xc0, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00};
+
+static unsigned char dnd_copy_mask_bits[] =
+{
+ 0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x1f,
+ 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
+ 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
+ 0x07, 0x06, 0xfc, 0x1f, 0x07, 0x0e, 0xfc, 0x1f, 0x07, 0x1e, 0x1c, 0x00,
+ 0x07, 0x3e, 0x1c, 0x00, 0x07, 0x7e, 0x1c, 0x00, 0x07, 0xfe, 0x1c, 0x00,
+ 0x07, 0xfe, 0x1d, 0x00, 0x07, 0xfe, 0x1f, 0x00, 0x07, 0xfe, 0x1f, 0x00,
+ 0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0x1e, 0x00, 0xff, 0xef, 0x1f, 0x00,
+ 0x00, 0xe6, 0x01, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00,
+ 0x00, 0x80, 0x01, 0x00};
+
+static unsigned char dnd_move_cursor_bits[] =
+{
+ 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08,
+ 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08,
+ 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x04, 0x08, 0x02, 0x0c, 0x08,
+ 0x02, 0x1c, 0x08, 0x02, 0x3c, 0x08, 0x02, 0x7c, 0x08, 0x02, 0xfc, 0x08,
+ 0x02, 0xfc, 0x09, 0x02, 0xfc, 0x0b, 0x02, 0x7c, 0x08, 0xfe, 0x6d, 0x0f,
+ 0x00, 0xc4, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x80, 0x01, 0x00, 0x80, 0x01,
+ 0x00, 0x00, 0x00};
+
+static unsigned char dnd_move_mask_bits[] =
+{
+ 0xff, 0xff, 0x1f, 0xff, 0xff, 0x1f, 0xff, 0xff, 0x1f, 0x07, 0x00, 0x1c,
+ 0x07, 0x00, 0x1c, 0x07, 0x00, 0x1c, 0x07, 0x00, 0x1c, 0x07, 0x00, 0x1c,
+ 0x07, 0x00, 0x1c, 0x07, 0x06, 0x1c, 0x07, 0x0e, 0x1c, 0x07, 0x1e, 0x1c,
+ 0x07, 0x3e, 0x1c, 0x07, 0x7e, 0x1c, 0x07, 0xfe, 0x1c, 0x07, 0xfe, 0x1d,
+ 0x07, 0xfe, 0x1f, 0x07, 0xfe, 0x1f, 0xff, 0xff, 0x1f, 0xff, 0xff, 0x1e,
+ 0xff, 0xef, 0x1f, 0x00, 0xe6, 0x01, 0x00, 0xc0, 0x03, 0x00, 0xc0, 0x03,
+ 0x00, 0x80, 0x01};
+
+static unsigned char dnd_link_cursor_bits[] =
+{
+ 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0x00, 0x02, 0x00, 0x08, 0x01,
+ 0x02, 0x00, 0x88, 0x00, 0x02, 0x00, 0x48, 0x00, 0x02, 0x00, 0xe8, 0x0f,
+ 0x02, 0x00, 0x48, 0x00, 0x02, 0x00, 0x88, 0x00, 0x02, 0x00, 0x08, 0x01,
+ 0x02, 0x00, 0x08, 0x00, 0x02, 0x04, 0x08, 0x00, 0x02, 0x0c, 0x08, 0x00,
+ 0x02, 0x1c, 0x08, 0x00, 0x02, 0x3c, 0x08, 0x00, 0x02, 0x7c, 0x08, 0x00,
+ 0x02, 0xfc, 0x08, 0x00, 0x02, 0xfc, 0x09, 0x00, 0x02, 0xfc, 0x0b, 0x00,
+ 0x02, 0x7c, 0x08, 0x00, 0xfe, 0x6d, 0x0f, 0x00, 0x00, 0xc4, 0x00, 0x00,
+ 0x00, 0xc0, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00};
+
+static unsigned char dnd_link_mask_bits[] =
+{
+ 0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x1f,
+ 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
+ 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
+ 0x07, 0x06, 0xfc, 0x1f, 0x07, 0x0e, 0xfc, 0x1f, 0x07, 0x1e, 0x1c, 0x00,
+ 0x07, 0x3e, 0x1c, 0x00, 0x07, 0x7e, 0x1c, 0x00, 0x07, 0xfe, 0x1c, 0x00,
+ 0x07, 0xfe, 0x1d, 0x00, 0x07, 0xfe, 0x1f, 0x00, 0x07, 0xfe, 0x1f, 0x00,
+ 0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0x1e, 0x00, 0xff, 0xef, 0x1f, 0x00,
+ 0x00, 0xe6, 0x01, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00,
+ 0x00, 0x80, 0x01, 0x00};
+
+static unsigned char dnd_ask_cursor_bits[] =
+{
+ 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0x00, 0x02, 0x00, 0x88, 0x03,
+ 0x02, 0x00, 0x48, 0x04, 0x02, 0x00, 0x08, 0x04, 0x02, 0x00, 0x08, 0x02,
+ 0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0x08, 0x01, 0x02, 0x00, 0x08, 0x00,
+ 0x02, 0x00, 0x08, 0x01, 0x02, 0x04, 0x08, 0x00, 0x02, 0x0c, 0x08, 0x00,
+ 0x02, 0x1c, 0x08, 0x00, 0x02, 0x3c, 0x08, 0x00, 0x02, 0x7c, 0x08, 0x00,
+ 0x02, 0xfc, 0x08, 0x00, 0x02, 0xfc, 0x09, 0x00, 0x02, 0xfc, 0x0b, 0x00,
+ 0x02, 0x7c, 0x08, 0x00, 0xfe, 0x6d, 0x0f, 0x00, 0x00, 0xc4, 0x00, 0x00,
+ 0x00, 0xc0, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00};
+
+static unsigned char dnd_ask_mask_bits[] =
+{
+ 0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x1f,
+ 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
+ 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f, 0x07, 0x00, 0xfc, 0x1f,
+ 0x07, 0x06, 0xfc, 0x1f, 0x07, 0x0e, 0xfc, 0x1f, 0x07, 0x1e, 0x1c, 0x00,
+ 0x07, 0x3e, 0x1c, 0x00, 0x07, 0x7e, 0x1c, 0x00, 0x07, 0xfe, 0x1c, 0x00,
+ 0x07, 0xfe, 0x1d, 0x00, 0x07, 0xfe, 0x1f, 0x00, 0x07, 0xfe, 0x1f, 0x00,
+ 0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0x1e, 0x00, 0xff, 0xef, 0x1f, 0x00,
+ 0x00, 0xe6, 0x01, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00,
+ 0x00, 0x80, 0x01, 0x00};
+
+static DndCursor dnd_cursors[] =
+{
+ {29, 25, 10, 10, dnd_copy_cursor_bits, dnd_copy_mask_bits, "XdndActionCopy", 0, 0, 0, 0},
+ {21, 25, 10, 10, dnd_move_cursor_bits, dnd_move_mask_bits, "XdndActionMove", 0, 0, 0, 0},
+ {29, 25, 10, 10, dnd_link_cursor_bits, dnd_link_mask_bits, "XdndActionLink", 0, 0, 0, 0},
+ {29, 25, 10, 10, dnd_ask_cursor_bits, dnd_ask_mask_bits, "XdndActionAsk", 0, 0, 0, 0},
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+};
+
+void xdnd_reset (DndClass * dnd)
+{
+ dnd->stage = XDND_DROP_STAGE_IDLE;
+ dnd->dragging_version = 0;
+ dnd->internal_drag = 0;
+ dnd->want_position = 0;
+ dnd->ready_to_drop = 0;
+ dnd->will_accept = 0;
+ dnd->rectangle.x = dnd->rectangle.y = 0;
+ dnd->rectangle.width = dnd->rectangle.height = 0;
+ dnd->dropper_window = 0;
+ dnd->dropper_toplevel = 0;
+ dnd->dragger_window = 0;
+ dnd->dragger_typelist = 0;
+ dnd->desired_type = 0;
+ dnd->time = 0;
+}
+
+void xdnd_init (DndClass * dnd, Display * display)
+{
+ DndCursor *cursor;
+ XColor black, white;
+ memset (dnd, 0, sizeof (*dnd));
+
+ dnd->display = display;
+ dnd->root_window = DefaultRootWindow (display);
+ dnd->version = XDND_VERSION;
+
+ dnd->XdndAware = XInternAtom (dnd->display, "XdndAware", False);
+ dnd->XdndSelection = XInternAtom (dnd->display, "XdndSelection", False);
+ dnd->XdndEnter = XInternAtom (dnd->display, "XdndEnter", False);
+ dnd->XdndLeave = XInternAtom (dnd->display, "XdndLeave", False);
+ dnd->XdndPosition = XInternAtom (dnd->display, "XdndPosition", False);
+ dnd->XdndDrop = XInternAtom (dnd->display, "XdndDrop", False);
+ dnd->XdndFinished = XInternAtom (dnd->display, "XdndFinished", False);
+ dnd->XdndStatus = XInternAtom (dnd->display, "XdndStatus", False);
+ dnd->XdndActionCopy = XInternAtom (dnd->display, "XdndActionCopy", False);
+ dnd->XdndActionMove = XInternAtom (dnd->display, "XdndActionMove", False);
+ dnd->XdndActionLink = XInternAtom (dnd->display, "XdndActionLink", False);
+ dnd->XdndActionAsk = XInternAtom (dnd->display, "XdndActionAsk", False);
+ dnd->XdndActionPrivate = XInternAtom (dnd->display, "XdndActionPrivate", False);
+ dnd->XdndTypeList = XInternAtom (dnd->display, "XdndTypeList", False);
+ dnd->XdndActionList = XInternAtom (dnd->display, "XdndActionList", False);
+ dnd->XdndActionDescription = XInternAtom (dnd->display, "XdndActionDescription", False);
+
+ dnd->Xdnd_NON_PROTOCOL_ATOM = XInternAtom (dnd->display, "JXSelectionWindowProperty", False);
+
+ xdnd_reset (dnd);
+
+ dnd->cursors = dnd_cursors;
+
+ black.pixel = BlackPixel (dnd->display, DefaultScreen (dnd->display));
+ white.pixel = WhitePixel (dnd->display, DefaultScreen (dnd->display));
+
+ XQueryColor (dnd->display, DefaultColormap (dnd->display, DefaultScreen (dnd->display)), &black);
+ XQueryColor (dnd->display, DefaultColormap (dnd->display, DefaultScreen (dnd->display)), &white);
+
+ for (cursor = &dnd->cursors[0]; cursor->width; cursor++) {
+ cursor->image_pixmap = XCreateBitmapFromData \
+ (dnd->display, dnd->root_window, (char *) cursor->image_data, cursor->width, cursor->height);
+ cursor->mask_pixmap = XCreateBitmapFromData \
+ (dnd->display, dnd->root_window, (char *) cursor->mask_data, cursor->width, cursor->height);
+ cursor->cursor = XCreatePixmapCursor (dnd->display, cursor->image_pixmap,
+ cursor->mask_pixmap, &black, &white, cursor->x, cursor->y);
+ XFreePixmap (dnd->display, cursor->image_pixmap);
+ XFreePixmap (dnd->display, cursor->mask_pixmap);
+ cursor->action = XInternAtom (dnd->display, cursor->_action, False);
+ }
+}
+
+void xdnd_shut (DndClass * dnd)
+{
+ DndCursor *cursor;
+ for (cursor = &dnd->cursors[0]; cursor->width; cursor++)
+ XFreeCursor (dnd->display, cursor->cursor);
+ memset (dnd, 0, sizeof (*dnd));
+ return;
+}
+
+
+/* typelist is a null terminated array */
+static int array_length (Atom * a)
+{
+ int n;
+ for (n = 0; a[n]; n++);
+ return n;
+}
+
+void xdnd_set_dnd_aware (DndClass * dnd, Window window, Atom * typelist)
+{
+ Window root_return, parent;
+ unsigned int nchildren_return;
+ Window *children_return = 0;
+ int r, s;
+ if(!window) return;
+ if (dnd->widget_exists)
+ if (!(*dnd->widget_exists) (dnd, window))
+ return;
+ s = XChangeProperty (dnd->display, window, dnd->XdndAware, XA_ATOM, 32, PropModeReplace,
+ (unsigned char *) &dnd->version, 1);
+#if 1
+ dnd_debug4 ("XChangeProperty() = %d, window = %ld, widget = %s", s, window, "<WIDGET>");
+#endif
+ if (s && typelist) {
+ int n;
+ n = array_length (typelist);
+ if (n)
+ s = XChangeProperty (dnd->display, window, dnd->XdndAware, XA_ATOM, 32, PropModeAppend,
+ (unsigned char *) typelist, n);
+ }
+ r =
+ XQueryTree (dnd->display, window, &root_return, &parent, &children_return,
+ &nchildren_return);
+ if (children_return)
+ XFree (children_return);
+ if (r && parent != root_return)
+ xdnd_set_dnd_aware (dnd, parent, typelist);
+}
+
+int xdnd_is_dnd_aware (DndClass * dnd, Window window, int *version, Atom * typelist)
+{
+ Atom actual;
+ int format;
+ unsigned long count, remaining;
+ unsigned char *data = 0;
+ Atom *types, *t;
+ int result = 1;
+
+ *version = 0;
+ XGetWindowProperty (dnd->display, window, dnd->XdndAware,
+ 0, 0x8000000L, False, XA_ATOM,
+ &actual, &format,
+ &count, &remaining, &data);
+
+ if (actual != XA_ATOM || format != 32 || count == 0 || !data) {
+ dnd_debug2 ("XGetWindowProperty failed in xdnd_is_dnd_aware - XdndAware = %ld", dnd->XdndAware);
+ if (data)
+ XFree (data);
+ return 0;
+ }
+ types = (Atom *) data;
+#if XDND_VERSION >= 3
+ if (types[0] < 3) {
+ if (data)
+ XFree (data);
+ return 0;
+ }
+#endif
+ *version = dnd->version < types[0] ? dnd->version : types[0]; /* minimum */
+ dnd_debug2 ("Using XDND version %d", *version);
+ if (count > 1) {
+ result = 0;
+ for (t = typelist; *t; t++) {
+ int j;
+ for (j = 1; j < count; j++) {
+ if (types[j] == *t) {
+ result = 1;
+ break;
+ }
+ }
+ if (result)
+ break;
+ }
+ }
+ XFree (data);
+ return result;
+}
+
+void xdnd_set_type_list (DndClass * dnd, Window window, Atom * typelist)
+{
+ int n;
+ n = array_length (typelist);
+ XChangeProperty (dnd->display, window, dnd->XdndTypeList, XA_ATOM, 32,
+ PropModeReplace, (unsigned char *) typelist, n);
+}
+
+/* result must be free'd */
+void xdnd_get_type_list (DndClass * dnd, Window window, Atom ** typelist)
+{
+ Atom type, *a;
+ int format, i;
+ unsigned long count, remaining;
+ unsigned char *data = NULL;
+
+ *typelist = 0;
+
+ XGetWindowProperty (dnd->display, window, dnd->XdndTypeList,
+ 0, 0x8000000L, False, XA_ATOM,
+ &type, &format, &count, &remaining, &data);
+
+ if (type != XA_ATOM || format != 32 || count == 0 || !data) {
+ if (data)
+ XFree (data);
+ dnd_debug2 ("XGetWindowProperty failed in xdnd_get_type_list - dnd->XdndTypeList = %ld", dnd->XdndTypeList);
+ return;
+ }
+ *typelist = malloc ((count + 1) * sizeof (Atom));
+ a = (Atom *) data;
+ for (i = 0; i < count; i++)
+ (*typelist)[i] = a[i];
+ (*typelist)[count] = 0;
+
+ XFree (data);
+}
+
+void xdnd_get_three_types (DndClass * dnd, XEvent * xevent, Atom ** typelist)
+{
+ int i;
+
(void) dnd;
+
+ *typelist = malloc ((XDND_THREE + 1) * sizeof (Atom));
+ for (i = 0; i < XDND_THREE; i++)
+ (*typelist)[i] = XDND_ENTER_TYPE (xevent, i);
+ (*typelist)[XDND_THREE] = 0; /* although (*typelist)[1] or (*typelist)[2] may also be set to nill */
+}
+
+/* result must be free'd */
+static char *concat_string_list (char **t, int *bytes)
+{
+ int l, n;
+ char *s;
+ for (l = n = 0;; n++) {
+ if (!t[n])
+ break;
+ if (!t[n][0])
+ break;
+ l += strlen (t[n]) + 1;
+ }
+ s = malloc (l + 1);
+ for (l = n = 0;; n++) {
+ if (!t[n])
+ break;
+ if (!(t[n][0]))
+ break;
+ int t_size = strlen (t[n]) + 1;
+ memcpy (s + l, t[n], t_size);
+ l += t_size;
+ }
+ *bytes = l;
+ s[l] = '\0';
+ return s;
+}
+
+void xdnd_set_actions (DndClass * dnd, Window window, Atom * actions, char **descriptions)
+{
+ int n, l;
+ char *s;
+ n = array_length (actions);
+
+ XChangeProperty (dnd->display, window, dnd->XdndActionList, XA_ATOM, 32,
+ PropModeReplace, (unsigned char *) actions, n);
+
+ s = concat_string_list (descriptions, &l);
+ XChangeProperty (dnd->display, window, dnd->XdndActionList, XA_STRING, 8,
+ PropModeReplace, (unsigned char *) s, l);
+ xdnd_xfree (s);
+}
+
+/* returns 1 on error or no actions, otherwise result must be free'd
+ xdnd_get_actions (window, &actions, &descriptions);
+ free (actions); free (descriptions); */
+int xdnd_get_actions (DndClass * dnd, Window window, Atom ** actions, char ***descriptions)
+{
+ Atom type, *a;
+ int format, i;
+ unsigned long count, dcount, remaining;
+ unsigned char *data = 0, *r;
+
+ *actions = 0;
+ *descriptions = 0;
+ XGetWindowProperty (dnd->display, window, dnd->XdndActionList,
+ 0, 0x8000000L, False, XA_ATOM,
+ &type, &format, &count, &remaining, &data);
+
+ if (type != XA_ATOM || format != 32 || count == 0 || !data) {
+ if (data)
+ XFree (data);
+ return 1;
+ }
+ *actions = malloc ((count + 1) * sizeof (Atom));
+ a = (Atom *) data;
+ for (i = 0; i < count; i++)
+ (*actions)[i] = a[i];
+ (*actions)[count] = 0;
+
+ XFree (data);
+
+ data = 0;
+ XGetWindowProperty (dnd->display, window, dnd->XdndActionDescription,
+ 0, 0x8000000L, False, XA_STRING, &type, &format,
+ &dcount, &remaining, &data);
+
+ if (type != XA_STRING || format != 8 || dcount == 0) {
+ if (data)
+ XFree (data);
+ *descriptions = malloc ((count + 1) * sizeof (char *));
+ dnd_warning ("XGetWindowProperty no property or wrong format for action descriptions");
+ for (i = 0; i < count; i++)
+ (*descriptions)[i] = "";
+ (*descriptions)[count] = 0;
+ } else {
+ int l;
+ l = (count + 1) * sizeof (char *);
+ *descriptions = malloc (l + dcount);
+ memcpy (*descriptions + l, data, dcount);
+ XFree (data);
+ data = (unsigned char *) *descriptions;
+ data += l;
+ l = 0;
+ for (i = 0, r = data;; r += l + 1, i++) {
+ l = strlen ((char *) r);
+ if (!l || i >= count)
+ break;
+ (*descriptions)[i] = (char *) r;
+ }
+ for (; i < count; i++) {
+ (*descriptions)[i] = "";
+ }
+ (*descriptions)[count] = 0;
+ }
+ return 0;
+}
+
+/* returns non-zero on cancel */
+int xdnd_choose_action_dialog (DndClass * dnd, Atom * actions, char **descriptions, Atom * result)
+{
+ if (!actions[0])
+ return 1;
+ if (!dnd->action_choose_dialog) { /* default to return the first action if no dialog set */
+ *result = actions[0];
+ return 0;
+ }
+ return (*dnd->action_choose_dialog) (dnd, descriptions, actions, result);
+}
+
+static void xdnd_send_event (DndClass * dnd, Window window, XEvent * xevent)
+{
+ dnd_debug4 ("xdnd_send_event(), window = %ld, l[0] = %ld, l[4] = %ld",
+ window, xevent->xclient.data.l[0], xevent->xclient.data.l[4]);
+ dnd_debug2 ("xdnd_send_event(), from widget widget %s", (char *) "<WIDGET>");
+ XSendEvent (dnd->display, window, 0, 0, xevent);
+}
+
+static void xdnd_send_enter (DndClass * dnd, Window window, Window from, Atom * typelist)
+{
+ XEvent xevent;
+ int n, i;
+ n = array_length (typelist);
+
+ memset (&xevent, 0, sizeof (xevent));
+
+ xevent.xany.type = ClientMessage;
+ xevent.xany.display = dnd->display;
+ xevent.xclient.window = window;
+ xevent.xclient.message_type = dnd->XdndEnter;
+ xevent.xclient.format = 32;
+
+ XDND_ENTER_SOURCE_WIN (&xevent) = from;
+ XDND_ENTER_THREE_TYPES_SET (&xevent, n > XDND_THREE);
+ XDND_ENTER_VERSION_SET (&xevent, dnd->version);
+ for (i = 0; i < n && i < XDND_THREE; i++)
+ XDND_ENTER_TYPE (&xevent, i) = typelist[i];
+ xdnd_send_event (dnd, window, &xevent);
+}
+
+static void xdnd_send_position (DndClass * dnd, Window window, Window from, Atom action, int x, int y, unsigned long time)
+{
+ XEvent xevent;
+
+ memset (&xevent, 0, sizeof (xevent));
+
+ xevent.xany.type = ClientMessage;
+ xevent.xany.display = dnd->display;
+ xevent.xclient.window = window;
+ xevent.xclient.message_type = dnd->XdndPosition;
+ xevent.xclient.format = 32;
+
+ XDND_POSITION_SOURCE_WIN (&xevent) = from;
+ XDND_POSITION_ROOT_SET (&xevent, x, y);
+ if (dnd_version_at_least (dnd->dragging_version, 1))
+ XDND_POSITION_TIME (&xevent) = time;
+ if (dnd_version_at_least (dnd->dragging_version, 2))
+ XDND_POSITION_ACTION (&xevent) = action;
+
+ xdnd_send_event (dnd, window, &xevent);
+}
+
+static void xdnd_send_status (DndClass * dnd, Window window, Window from, int will_accept, \
+ int want_position, int x, int y, int w, int h, Atom action)
+{
+ XEvent xevent;
+
+ memset (&xevent, 0, sizeof (xevent));
+
+ xevent.xany.type = ClientMessage;
+ xevent.xany.display = dnd->display;
+ xevent.xclient.window = window;
+ xevent.xclient.message_type = dnd->XdndStatus;
+ xevent.xclient.format = 32;
+
+ XDND_STATUS_TARGET_WIN (&xevent) = from;
+ XDND_STATUS_WILL_ACCEPT_SET (&xevent, will_accept);
+ if (will_accept)
+ XDND_STATUS_WANT_POSITION_SET (&xevent, want_position);
+ if (want_position)
+ XDND_STATUS_RECT_SET (&xevent, x, y, w, h);
+ if (dnd_version_at_least (dnd->dragging_version, 2))
+ if (will_accept)
+ XDND_STATUS_ACTION (&xevent) = action;
+
+ xdnd_send_event (dnd, window, &xevent);
+}
+
+static void xdnd_send_leave (DndClass * dnd, Window window, Window from)
+{
+ XEvent xevent;
+
+ memset (&xevent, 0, sizeof (xevent));
+
+ xevent.xany.type = ClientMessage;
+ xevent.xany.display = dnd->display;
+ xevent.xclient.window = window;
+ xevent.xclient.message_type = dnd->XdndLeave;
+ xevent.xclient.format = 32;
+
+ XDND_LEAVE_SOURCE_WIN (&xevent) = from;
+
+ xdnd_send_event (dnd, window, &xevent);
+}
+
+static void xdnd_send_drop (DndClass * dnd, Window window, Window from, unsigned long time)
+{
+ XEvent xevent;
+
+ memset (&xevent, 0, sizeof (xevent));
+
+ xevent.xany.type = ClientMessage;
+ xevent.xany.display = dnd->display;
+ xevent.xclient.window = window;
+ xevent.xclient.message_type = dnd->XdndDrop;
+ xevent.xclient.format = 32;
+
+ XDND_DROP_SOURCE_WIN (&xevent) = from;
+ if (dnd_version_at_least (dnd->dragging_version, 1))
+ XDND_DROP_TIME (&xevent) = time;
+
+ xdnd_send_event (dnd, window, &xevent);
+}
+
+/* error is not actually used, i think future versions of the protocol should return an error status
+ to the calling window with the XdndFinished client message */
+static void xdnd_send_finished (DndClass * dnd, Window window, Window from, int error)
+{
+ XEvent xevent;
+
(void) error;
+
+ memset (&xevent, 0, sizeof (xevent));
+
+ xevent.xany.type = ClientMessage;
+ xevent.xany.display = dnd->display;
+ xevent.xclient.window = window;
+ xevent.xclient.message_type = dnd->XdndFinished;
+ xevent.xclient.format = 32;
+
+ XDND_FINISHED_TARGET_WIN (&xevent) = from;
+
+ xdnd_send_event (dnd, window, &xevent);
+}
+
+/* returns non-zero on error - i.e. no selection owner set. Type is of course the mime type */
+static int xdnd_convert_selection (DndClass * dnd, Window window, Window requester, Atom type)
+{
+ if (!(window = XGetSelectionOwner (dnd->display, dnd->XdndSelection))) {
+ dnd_debug1 ("xdnd_convert_selection(): XGetSelectionOwner failed");
+ return 1;
+ }
+ XConvertSelection (dnd->display, dnd->XdndSelection, type,
+ dnd->Xdnd_NON_PROTOCOL_ATOM, requester, CurrentTime);
+ return 0;
+}
+
+/* returns non-zero on error */
+static int xdnd_set_selection_owner (DndClass * dnd, Window window, Atom type, Time time)
+{
+ (void) type;
+
+ if (!XSetSelectionOwner (dnd->display, dnd->XdndSelection, window, time)) {
+ dnd_debug1 ("xdnd_set_selection_owner(): XSetSelectionOwner failed");
+ return 1;
+ }
+ return 0;
+}
+
+static void xdnd_selection_send (DndClass * dnd, XSelectionRequestEvent * request, unsigned char *data, int length)
+{
+ XEvent xevent;
+ dnd_debug2 (" requestor = %ld", request->requestor);
+ dnd_debug2 (" property = %ld", request->property);
+ dnd_debug2 (" length = %d", length);
+ XChangeProperty (dnd->display, request->requestor, request->property,
+ request->target, 8, PropModeReplace, data, length);
+ xevent.xselection.type = SelectionNotify;
+ xevent.xselection.property = request->property;
+ xevent.xselection.display = request->display;
+ xevent.xselection.requestor = request->requestor;
+ xevent.xselection.selection = request->selection;
+ xevent.xselection.target = request->target;
+ xevent.xselection.time = request->time;
+ xdnd_send_event (dnd, request->requestor, &xevent);
+}
+
+#if 0
+/* respond to a notification that a primary selection has been sent */
+int xdnd_get_selection (DndClass * dnd, Window from, Atom property, Window insert)
+{
+ long read;
+ int error = 0;
+ unsigned long remaining;
+ if (!property)
+ return 1;
+ read = 0;
+ do {
+ unsigned char *s;
+ Atom actual;
+ int format;
+ unsigned long count;
+ if (XGetWindowProperty (dnd->display, insert, property, read / 4, 65536, 1,
+ AnyPropertyType, &actual, &format,
+ &count, &remaining,
+ &s) != Success) {
+ XFree (s);
+ return 1;
+ }
+ read += count;
+ if (dnd->widget_insert_drop && !error)
+ error = (*dnd->widget_insert_drop) (dnd, s, count, remaining, insert, from, actual);
+ XFree (s);
+ } while (remaining);
+ return error;
+}
+#endif
+
+static int paste_prop_internal (DndClass * dnd, Window from, Window insert, unsigned long prop, int delete_prop)
+{
+ long nread = 0;
+ unsigned long nitems;
+ unsigned long bytes_after;
+ int error = 0;
+ do {
+ Atom actual_type;
+ int actual_fmt;
+ unsigned char *s = 0;
+ if (XGetWindowProperty (dnd->display, insert, prop,
+ nread / 4, 65536, delete_prop,
+ AnyPropertyType, &actual_type, &actual_fmt,
+ &nitems, &bytes_after, &s) != Success) {
+ XFree (s);
+ return 1;
+ }
+ nread += nitems;
+ if (dnd->widget_insert_drop && !error)
+ error = (*dnd->widget_insert_drop) (dnd, s, nitems, bytes_after, insert, from, actual_fmt);
+ XFree (s);
+ } while (bytes_after);
+ if (!nread)
+ return 1;
+ return 0;
+}
+
+/*
+ * Respond to a notification that a primary selection has been sent (supports INCR)
+ */
+static int xdnd_get_selection (DndClass * dnd, Window from, Atom prop, Window insert)
+{
+ struct timeval tv, tv_start;
+ unsigned long bytes_after;
+ Atom actual_type;
+ int actual_fmt;
+ unsigned long nitems;
+ unsigned char *s = 0;
+ if (prop == None)
+ return 1;
+ if (XGetWindowProperty
+ (dnd->display, insert, prop, 0, 8, False, AnyPropertyType, &actual_type, &actual_fmt,
+ &nitems, &bytes_after, &s) != Success) {
+ XFree (s);
+ return 1;
+ }
+ XFree (s);
+ if (actual_type != XInternAtom (dnd->display, "INCR", False))
+ return paste_prop_internal (dnd, from, insert, prop, True);
+ XDeleteProperty (dnd->display, insert, prop);
+ gettimeofday (&tv_start, 0);
+ for (;;) {
+ long t;
+ fd_set r;
+ XEvent xe;
+ if (XCheckMaskEvent (dnd->display, PropertyChangeMask, &xe)) {
+ if (xe.type == PropertyNotify && xe.xproperty.state == PropertyNewValue) {
+/* time between arrivals of data */
+ gettimeofday (&tv_start, 0);
+ if (paste_prop_internal (dnd, from, insert, prop, True))
+ break;
+ }
+ } else {
+ tv.tv_sec = 0;
+ tv.tv_usec = 10000;
+ FD_ZERO (&r);
+ FD_SET (ConnectionNumber (dnd->display), &r);
+ select (ConnectionNumber (dnd->display) + 1, &r, 0, 0, &tv);
+ if (FD_ISSET (ConnectionNumber (dnd->display), &r))
+ continue;
+ }
+ gettimeofday (&tv, 0);
+ t = (tv.tv_sec - tv_start.tv_sec) * 1000000L + (tv.tv_usec - tv_start.tv_usec);
+/* no data for five seconds, so quit */
+ if (t > 5000000L)
+ return 1;
+ }
+ return 0;
+}
+
+
+int outside_rectangle (int x, int y, XRectangle * r)
+{
+ return (x < r->x || y < r->y || x >= r->x + r->width || y >= r->y + r->height);
+}
+
+/* avoids linking with the maths library */
+static float xdnd_sqrt (float x)
+{
+ float last_ans, ans = 2, a;
+ if (x <= 0.0f)
+ return 0.0f;
+ do {
+ last_ans = ans;
+ ans = (ans + x / ans) / 2;
+ a = (ans - last_ans) / ans;
+ if (a < 0.0f)
+ a = (-a);
+ } while (a > 0.001f);
+ return ans;
+}
+
+/* returns action on success, 0 otherwise */
+Atom xdnd_drag (DndClass * dnd, Window from, Atom action, Atom * typelist)
+{
+ XEvent xevent, xevent_temp;
+ Window over_window = 0, last_window = 0;
+#if XDND_VERSION >= 3
+ Window last_dropper_toplevel = 0;
+ int internal_dropable = 1;
+#endif
+ int n;
+ DndCursor *cursor;
+ float x_mouse, y_mouse;
+ int result = 0, dnd_aware;
+
+ if (!typelist)
+ dnd_warning ("xdnd_drag() called with typelist = 0");
+
+/* first wait until the mouse moves more than five pixels */
+ do {
+ XNextEvent (dnd->display, &xevent);
+ if (xevent.type == ButtonRelease) {
+ dnd_debug1 ("button release - no motion");
+ XSendEvent (dnd->display, xevent.xany.window, 0, ButtonReleaseMask, &xevent);
+ return 0;
+ }
+ } while (xevent.type != MotionNotify);
+
+ x_mouse = (float) xevent.xmotion.x_root;
+ y_mouse = (float) xevent.xmotion.y_root;
+
+ if (dnd->drag_threshold < 0.001f)
+ dnd->drag_threshold = 4.0f;
+ for (;;) {
+ XNextEvent (dnd->display, &xevent);
+ if (xevent.type == MotionNotify)
+ if (xdnd_sqrt ((x_mouse - xevent.xmotion.x_root) * (x_mouse - xevent.xmotion.x_root) +
+ (y_mouse - xevent.xmotion.y_root) * (y_mouse - xevent.xmotion.y_root)) > dnd->drag_threshold)
+ break;
+ if (xevent.type == ButtonRelease) {
+ XSendEvent (dnd->display, xevent.xany.window, 0, ButtonReleaseMask, &xevent);
+ return 0;
+ }
+ }
+
+ dnd_debug1 ("moved 5 pixels - going to drag");
+
+ n = array_length (typelist);
+ if (n > XDND_THREE)
+ xdnd_set_type_list (dnd, from, typelist);
+
+ xdnd_reset (dnd);
+
+ dnd->stage = XDND_DRAG_STAGE_DRAGGING;
+
+ for (cursor = &dnd->cursors[0]; cursor->width; cursor++)
+ if (cursor->action == action)
+ break;
+ if (!cursor->width)
+ cursor = &dnd->cursors[0];
+
+/* the mouse has been dragged a little, so this is a drag proper */
+ if (XGrabPointer (dnd->display, dnd->root_window, False,
+ ButtonMotionMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
+ GrabModeAsync, GrabModeAsync, None,
+ cursor->cursor, CurrentTime) != GrabSuccess)
+ dnd_debug1 ("Unable to grab pointer");
+
+
+ while (xevent.xany.type != ButtonRelease) {
+ XAllowEvents (dnd->display, SyncPointer, CurrentTime);
+ XNextEvent (dnd->display, &xevent);
+ switch (xevent.type) {
+ case Expose:
+ if (dnd->handle_expose_events)
+ (*dnd->handle_expose_events) (dnd, &xevent);
+ break;
+ case EnterNotify:
+/* this event is not actually reported, so we find out by ourselves from motion events */
+ break;
+ case LeaveNotify:
+/* this event is not actually reported, so we find out by ourselves from motion events */
+ break;
+ case ButtonRelease:
+/* done, but must send a leave event */
+ dnd_debug1 ("ButtonRelease - exiting event loop");
+ break;
+ case MotionNotify:
+ dnd_aware = 0;
+ dnd->dropper_toplevel = 0;
+ memcpy (&xevent_temp, &xevent, sizeof (xevent));
+ xevent.xmotion.subwindow = xevent.xmotion.window;
+ {
+ Window root_return, child_return;
+ int x_temp, y_temp;
+ unsigned int mask_return;
+ while (XQueryPointer (dnd->display, xevent.xmotion.subwindow, &root_return, &child_return,
+ &x_temp, &y_temp, &xevent.xmotion.x,
+ &xevent.xmotion.y, &mask_return)) {
+#if XDND_VERSION >= 3
+ if (!dnd_aware) {
+ if ((dnd_aware = xdnd_is_dnd_aware (dnd, xevent.xmotion.subwindow, &dnd->dragging_version, typelist))) {
+ dnd->dropper_toplevel = xevent.xmotion.subwindow;
+ xevent.xmotion.x_root = x_temp;
+ xevent.xmotion.y_root = y_temp;
+ }
+ }
+#else
+ xevent.xmotion.x_root = x_temp;
+ xevent.xmotion.y_root = y_temp;
+#endif
+ if (!child_return)
+ goto found_descendent;
+ xevent.xmotion.subwindow = child_return;
+ }
+ break;
+ }
+ found_descendent:
+
+/* last_window is just for debug purposes */
+ if (last_window != xevent.xmotion.subwindow) {
+ dnd_debug2 ("window crossing to %ld", xevent.xmotion.subwindow);
+ dnd_debug2 (" current window is %ld", over_window);
+ dnd_debug3 (" last_window = %ld, xmotion.subwindow = %ld", last_window, xevent.xmotion.subwindow);
+#if XDND_VERSION >= 3
+ dnd_debug3 (" dropper_toplevel = %ld, last_dropper_toplevel.subwindow = %ld", dnd->dropper_toplevel, last_dropper_toplevel);
+#endif
+ dnd_debug3 (" dnd_aware = %d, dnd->options & XDND_OPTION_NO_HYSTERESIS = %ld", dnd_aware, (long) dnd->options & XDND_OPTION_NO_HYSTERESIS);
+ }
+
+#if XDND_VERSION < 3
+/* is the new window dnd aware? if not stay in the old window */
+ if (over_window != xevent.xmotion.subwindow &&
+ last_window != xevent.xmotion.subwindow &&
+ (
+ (dnd_aware = xdnd_is_dnd_aware (dnd, xevent.xmotion.subwindow, &dnd->dragging_version, typelist))
+ ||
+ (dnd->options & XDND_OPTION_NO_HYSTERESIS)
+ ))
+#else
+ internal_dropable = 1;
+ if (dnd->widget_exists && (*dnd->widget_exists) (dnd, xevent.xmotion.subwindow))
+ if (!xdnd_is_dnd_aware (dnd, xevent.xmotion.subwindow, &dnd->dragging_version, typelist))
+ internal_dropable = 0;
+ dnd_debug3 ("dnd->dropper_toplevel = %ld, last_dropper_toplevel = %ld\n", dnd->dropper_toplevel, last_dropper_toplevel);
+ if ((dnd->dropper_toplevel != last_dropper_toplevel ||
+ last_window != xevent.xmotion.subwindow) && internal_dropable &&
+ (
+ (dnd_aware)
+ ||
+ (dnd->options & XDND_OPTION_NO_HYSTERESIS)
+ ))
+#endif
+ {
+/* leaving window we were over */
+ if (over_window) {
+ if (dnd->stage == XDND_DRAG_STAGE_ENTERED) {
+ dnd_debug1 ("got leave at right stage");
+ dnd->stage = XDND_DRAG_STAGE_DRAGGING;
+ if (dnd->internal_drag) {
+ dnd_debug1 (" our own widget");
+ if (dnd->widget_apply_leave)
+ (*dnd->widget_apply_leave) (dnd, over_window);
+ } else {
+ dnd_debug1 (" not our widget - sending XdndLeave");
+#if XDND_VERSION < 3
+ xdnd_send_leave (dnd, over_window, from);
+#else
+ if (dnd->dropper_toplevel != last_dropper_toplevel) {
+ xdnd_send_leave (dnd, last_dropper_toplevel, from);
+ } else {
+ dnd_debug1 (" not sending leave --> dnd->dropper_toplevel == last_dropper_toplevel");
+ }
+#endif
+ }
+ dnd->internal_drag = 0;
+ dnd->dropper_window = 0;
+ dnd->ready_to_drop = 0;
+ } else {
+ dnd_debug1 ("got leave at wrong stage - ignoring");
+ }
+ }
+/* entering window we are currently over */
+ over_window = xevent.xmotion.subwindow;
+ if (dnd_aware) {
+ dnd_debug1 (" is dnd aware");
+ dnd->stage = XDND_DRAG_STAGE_ENTERED;
+ if (dnd->widget_exists && (*dnd->widget_exists) (dnd, over_window))
+ dnd->internal_drag = 1;
+ if (dnd->internal_drag) {
+ dnd_debug1 (" our own widget");
+ } else {
+ dnd_debug2 (" not our widget - sending XdndEnter to %ld", over_window);
+#if XDND_VERSION < 3
+ xdnd_send_enter (dnd, over_window, from, typelist);
+#else
+ if (dnd->dropper_toplevel != last_dropper_toplevel)
+ xdnd_send_enter (dnd, dnd->dropper_toplevel, from, typelist);
+#endif
+ }
+ dnd->want_position = 1;
+ dnd->ready_to_drop = 0;
+ dnd->rectangle.width = dnd->rectangle.height = 0;
+ dnd->dropper_window = over_window;
+/* we want an additional motion event in case the pointer enters and then stops */
+ XSendEvent (dnd->display, from, 0, ButtonMotionMask, &xevent_temp);
+ XSync (dnd->display, 0);
+ }
+#if XDND_VERSION >= 3
+ last_dropper_toplevel = dnd->dropper_toplevel;
+#endif
+/* we are now officially in a new window */
+ } else {
+/* got here, so we are just moving `inside' the same window */
+ if (dnd->stage == XDND_DRAG_STAGE_ENTERED) {
+ dnd->supported_action = dnd->XdndActionCopy;
+ dnd_debug1 ("got motion at right stage");
+ dnd->x = xevent.xmotion.x_root;
+ dnd->y = xevent.xmotion.y_root;
+ if (dnd->want_position || outside_rectangle (dnd->x, dnd->y, &dnd->rectangle)) {
+ dnd_debug1 (" want position and outside rectangle");
+ if (dnd->internal_drag) {
+ dnd_debug1 (" our own widget");
+ dnd->ready_to_drop = (*dnd->widget_apply_position) (dnd, over_window, from,
+ action, dnd->x, dnd->y, xevent.xmotion.time, typelist,
+ &dnd->want_position, &dnd->supported_action, &dnd->desired_type, &dnd->rectangle);
+ /* if not ready, keep sending positions, this check is repeated below for XdndStatus from external widgets */
+ if (!dnd->ready_to_drop) {
+ dnd->want_position = 1;
+ dnd->rectangle.width = dnd->rectangle.height = 0;
+ }
+ dnd_debug2 (" return action=%ld", dnd->supported_action);
+ } else {
+#if XDND_VERSION < 3
+ dnd_debug3 (" not our own widget - sending XdndPosition to %ld, action %ld", over_window, action);
+ xdnd_send_position (dnd, over_window, from, action, dnd->x, dnd->y, xevent.xmotion.time);
+#else
+ dnd_debug3 (" not our own widget - sending XdndPosition to %ld, action %ld", dnd->dropper_toplevel, action);
+ xdnd_send_position (dnd, dnd->dropper_toplevel, from, action, dnd->x, dnd->y, xevent.xmotion.time);
+#endif
+ }
+ } else if (dnd->want_position) {
+ dnd_debug1 (" inside rectangle");
+ } else {
+ dnd_debug1 (" doesn't want position");
+ }
+ }
+ }
+ last_window = xevent.xmotion.subwindow;
+ break;
+ case ClientMessage:
+ dnd_debug1 ("ClientMessage recieved");
+ if (xevent.xclient.message_type == dnd->XdndStatus && !dnd->internal_drag) {
+ dnd_debug1 (" XdndStatus recieved");
+ if (dnd->stage == XDND_DRAG_STAGE_ENTERED
+#if XDND_VERSION < 3
+ && XDND_STATUS_TARGET_WIN (&xevent) == dnd->dropper_window
+#endif
+ ) {
+ dnd_debug1 (" XdndStatus stage correct, dropper window correct");
+ dnd->want_position = XDND_STATUS_WANT_POSITION (&xevent);
+ dnd->ready_to_drop = XDND_STATUS_WILL_ACCEPT (&xevent);
+ dnd->rectangle.x = XDND_STATUS_RECT_X (&xevent);
+ dnd->rectangle.y = XDND_STATUS_RECT_Y (&xevent);
+ dnd->rectangle.width = XDND_STATUS_RECT_WIDTH (&xevent);
+ dnd->rectangle.height = XDND_STATUS_RECT_HEIGHT (&xevent);
+ dnd->supported_action = dnd->XdndActionCopy;
+ if (dnd_version_at_least (dnd->dragging_version, 2))
+ dnd->supported_action = XDND_STATUS_ACTION (&xevent);
+ dnd_debug3 (" return action=%ld, ready=%d", dnd->supported_action, dnd->ready_to_drop);
+ /* if not ready, keep sending positions, this check is repeated above for internal widgets */
+ if (!dnd->ready_to_drop) {
+ dnd->want_position = 1;
+ dnd->rectangle.width = dnd->rectangle.height = 0;
+ }
+ dnd_debug3 (" rectangle = (x=%d, y=%d, ", dnd->rectangle.x, dnd->rectangle.y);
+ dnd_debug4 ("w=%d, h=%d), want_position=%d\n", dnd->rectangle.width, dnd->rectangle.height, dnd->want_position);
+ }
+#if XDND_VERSION < 3
+ else if (XDND_STATUS_TARGET_WIN (&xevent) != dnd->dropper_window) {
+ dnd_debug3 (" XdndStatus XDND_STATUS_TARGET_WIN (&xevent) = %ld, dnd->dropper_window = %ld", XDND_STATUS_TARGET_WIN (&xevent), dnd->dropper_window);
+ }
+#endif
+ else {
+ dnd_debug2 (" XdndStatus stage incorrect dnd->stage = %d", dnd->stage);
+ }
+ }
+ break;
+ case SelectionRequest:{
+/* the target widget MAY request data, so wait for SelectionRequest */
+ int length = 0;
+ unsigned char *data = 0;
+ dnd_debug1 ("SelectionRequest - getting widget data");
+
+ (*dnd->widget_get_data) (dnd, from, &data, &length, xevent.xselectionrequest.target);
+ if (data) {
+ dnd_debug1 (" sending selection");
+ xdnd_selection_send (dnd, &xevent.xselectionrequest, data, length);
+ xdnd_xfree (data);
+ }
+ }
+ break;
+ }
+ }
+
+ if (dnd->ready_to_drop) {
+ Time time;
+ dnd_debug1 ("ready_to_drop - sending XdndDrop");
+ time = xevent.xbutton.time;
+ if (dnd->internal_drag) {
+/* we are dealing with our own widget, no need to send drop events, just put the data straight */
+ int length = 0;
+ unsigned char *data = 0;
+ if (dnd->widget_insert_drop) {
+ (*dnd->widget_get_data) (dnd, from, &data, &length, dnd->desired_type);
+ if (data) {
+ if (!(*dnd->widget_insert_drop) (dnd, data, length, 0, dnd->dropper_window, from, dnd->desired_type)) {
+ result = dnd->supported_action; /* success - so return action to caller */
+ dnd_debug1 (" inserted data into widget - success");
+ } else {
+ dnd_debug1 (" inserted data into widget - failed");
+ }
+ xdnd_xfree (data);
+ } else {
+ dnd_debug1 (" got data from widget, but data is null");
+ }
+ }
+ } else {
+ xdnd_set_selection_owner (dnd, from, dnd->desired_type, time);
+#if XDND_VERSION < 3
+ xdnd_send_drop (dnd, dnd->dropper_window, from, time);
+#else
+ xdnd_send_drop (dnd, dnd->dropper_toplevel, from, time);
+#endif
+ }
+ if (!dnd->internal_drag)
+ for (;;) {
+ XAllowEvents (dnd->display, SyncPointer, CurrentTime);
+ XNextEvent (dnd->display, &xevent);
+ if (xevent.type == ClientMessage && xevent.xclient.message_type == dnd->XdndFinished) {
+ dnd_debug1 ("XdndFinished");
+#if XDND_VERSION < 3
+ if (XDND_FINISHED_TARGET_WIN (&xevent) == dnd->dropper_window) {
+#endif
+ dnd_debug2 (" source correct - exiting event loop, action=%ld", dnd->supported_action);
+ result = dnd->supported_action; /* success - so return action to caller */
+ break;
+#if XDND_VERSION < 3
+ }
+#endif
+ } else if (xevent.type == Expose) {
+ if (dnd->handle_expose_events)
+ (*dnd->handle_expose_events) (dnd, &xevent);
+ } else if (xevent.type == MotionNotify) {
+ if (xevent.xmotion.time > time + (dnd->time_out ? dnd->time_out * 1000 : 10000)) { /* allow a ten second timeout as default */
+ dnd_debug1 ("timeout - exiting event loop");
+ break;
+ }
+ } else if (xevent.type == SelectionRequest && xevent.xselectionrequest.selection == dnd->XdndSelection) {
+/* the target widget is going to request data, so check for SelectionRequest events */
+ int length = 0;
+ unsigned char *data = 0;
+
+ dnd_debug1 ("SelectionRequest - getting widget data");
+ (*dnd->widget_get_data) (dnd, from, &data, &length, xevent.xselectionrequest.target);
+ if (data) {
+ dnd_debug1 (" sending selection");
+ xdnd_selection_send (dnd, &xevent.xselectionrequest, data, length);
+ xdnd_xfree (data);
+ }
+/* don't wait for a XdndFinished event */
+ if (!dnd_version_at_least (dnd->dragging_version, 2))
+ break;
+ }
+ }
+ } else {
+ dnd_debug1 ("not ready_to_drop - ungrabbing pointer");
+ }
+ XUngrabPointer (dnd->display, CurrentTime);
+ xdnd_reset (dnd);
+ return result;
+}
+
+/* returns non-zero if event is handled */
+int xdnd_handle_drop_events (DndClass * dnd, XEvent * xevent)
+{
+ int result = 0;
+ if (xevent->type == SelectionNotify) {
+ dnd_debug1 ("got SelectionNotify");
+ if (xevent->xselection.property == dnd->Xdnd_NON_PROTOCOL_ATOM && dnd->stage == XDND_DROP_STAGE_CONVERTING) {
+ int error;
+ dnd_debug1 (" property is Xdnd_NON_PROTOCOL_ATOM - getting selection");
+ error = xdnd_get_selection (dnd, dnd->dragger_window, xevent->xselection.property, xevent->xany.window);
+/* error is not actually used, i think future versions of the protocol maybe should return
+ an error status to the calling window with the XdndFinished client message */
+ if (dnd_version_at_least (dnd->dragging_version, 2)) {
+#if XDND_VERSION >= 3
+ xdnd_send_finished (dnd, dnd->dragger_window, dnd->dropper_toplevel, error);
+#else
+ xdnd_send_finished (dnd, dnd->dragger_window, dnd->dropper_window, error);
+#endif
+ dnd_debug1 (" sending finished");
+ }
+ xdnd_xfree (dnd->dragger_typelist);
+ xdnd_reset (dnd);
+ dnd->stage = XDND_DROP_STAGE_IDLE;
+ result = 1;
+ } else {
+ dnd_debug1 (" property is not Xdnd_NON_PROTOCOL_ATOM - ignoring");
+ }
+ } else if (xevent->type == ClientMessage) {
+ dnd_debug2 ("got ClientMessage to xevent->xany.window = %ld", xevent->xany.window);
+ if (xevent->xclient.message_type == dnd->XdndEnter) {
+ dnd_debug2 (" message_type is XdndEnter, version = %ld", XDND_ENTER_VERSION (xevent));
+#if XDND_VERSION >= 3
+ if (XDND_ENTER_VERSION (xevent) < 3)
+ return 0;
+#endif
+ xdnd_reset (dnd);
+ dnd->dragger_window = XDND_ENTER_SOURCE_WIN (xevent);
+#if XDND_VERSION >= 3
+ dnd->dropper_toplevel = xevent->xany.window;
+ dnd->dropper_window = 0; /* enter goes to the top level window only,
+ so we don't really know what the
+ sub window is yet */
+#else
+ dnd->dropper_window = xevent->xany.window;
+#endif
+ xdnd_xfree (dnd->dragger_typelist);
+ if (XDND_ENTER_THREE_TYPES (xevent)) {
+ dnd_debug1 (" three types only");
+ xdnd_get_three_types (dnd, xevent, &dnd->dragger_typelist);
+ } else {
+ dnd_debug1 (" more than three types - getting list");
+ xdnd_get_type_list (dnd, dnd->dragger_window, &dnd->dragger_typelist);
+ }
+ if (dnd->dragger_typelist)
+ dnd->stage = XDND_DROP_STAGE_ENTERED;
+ else
+ dnd_debug1 (" typelist returned as zero!");
+ dnd->dragging_version = XDND_ENTER_VERSION (xevent);
+ result = 1;
+ } else if (xevent->xclient.message_type == dnd->XdndLeave) {
+#if XDND_VERSION >= 3
+ if (xevent->xany.window == dnd->dropper_toplevel && dnd->dropper_window)
+ xevent->xany.window = dnd->dropper_window;
+#endif
+ dnd_debug1 (" message_type is XdndLeave");
+ if (dnd->dragger_window == XDND_LEAVE_SOURCE_WIN (xevent) && dnd->stage == XDND_DROP_STAGE_ENTERED) {
+ dnd_debug1 (" leaving");
+ if (dnd->widget_apply_leave)
+ (*dnd->widget_apply_leave) (dnd, xevent->xany.window);
+ dnd->stage = XDND_DROP_STAGE_IDLE;
+ xdnd_xfree (dnd->dragger_typelist);
+ result = 1;
+ dnd->dropper_toplevel = dnd->dropper_window = 0;
+ } else {
+ dnd_debug1 (" wrong stage or from wrong window");
+ }
+ } else if (xevent->xclient.message_type == dnd->XdndPosition) {
+ dnd_debug2 (" message_type is XdndPosition to %ld", xevent->xany.window);
+ if (dnd->dragger_window == XDND_POSITION_SOURCE_WIN (xevent) && dnd->stage == XDND_DROP_STAGE_ENTERED) {
+ int want_position;
+ Atom action;
+ XRectangle rectangle;
+ Window last_window;
+ last_window = dnd->dropper_window;
+#if XDND_VERSION >= 3
+/* version 3 gives us the top-level window only. WE have to find the child that the pointer is over: */
+ if (1 || xevent->xany.window != dnd->dropper_toplevel || !dnd->dropper_window) {
+ Window parent, child, new_child = 0;
+ dnd->dropper_toplevel = xevent->xany.window;
+ parent = dnd->root_window;
+ child = dnd->dropper_toplevel;
+ for (;;) {
+ int xd, yd;
+ new_child = 0;
+ if (!XTranslateCoordinates (dnd->display, parent, child,
+ XDND_POSITION_ROOT_X (xevent), XDND_POSITION_ROOT_Y (xevent),
+ &xd, &yd, &new_child))
+ break;
+ if (!new_child)
+ break;
+ child = new_child;
+ }
+ dnd->dropper_window = xevent->xany.window = child;
+ dnd_debug2 (" child window translates to %ld", dnd->dropper_window);
+ } else if (xevent->xany.window == dnd->dropper_toplevel && dnd->dropper_window) {
+ xevent->xany.window = dnd->dropper_window;
+ dnd_debug2 (" child window previously found: %ld", dnd->dropper_window);
+ }
+#endif
+ action = dnd->XdndActionCopy;
+ dnd->supported_action = dnd->XdndActionCopy;
+ dnd->x = XDND_POSITION_ROOT_X (xevent);
+ dnd->y = XDND_POSITION_ROOT_Y (xevent);
+ dnd->time = CurrentTime;
+ if (dnd_version_at_least (dnd->dragging_version, 1))
+ dnd->time = XDND_POSITION_TIME (xevent);
+ if (dnd_version_at_least (dnd->dragging_version, 1))
+ action = XDND_POSITION_ACTION (xevent);
+#if XDND_VERSION >= 3
+ if (last_window && last_window != xevent->xany.window)
+ if (dnd->widget_apply_leave)
+ (*dnd->widget_apply_leave) (dnd, last_window);
+#endif
+ dnd->will_accept = (*dnd->widget_apply_position) (dnd, xevent->xany.window, dnd->dragger_window,
+ action, dnd->x, dnd->y, dnd->time, dnd->dragger_typelist,
+ &want_position, &dnd->supported_action, &dnd->desired_type, &rectangle);
+ dnd_debug2 (" will accept = %d", dnd->will_accept);
+#if XDND_VERSION >= 3
+ dnd_debug2 (" sending status of %ld", dnd->dropper_toplevel);
+ xdnd_send_status (dnd, dnd->dragger_window, dnd->dropper_toplevel, dnd->will_accept,
+ want_position, rectangle.x, rectangle.y, rectangle.width, rectangle.height, dnd->supported_action);
+#else
+ dnd_debug2 (" sending status of %ld", xevent->xany.window);
+ xdnd_send_status (dnd, dnd->dragger_window, xevent->xany.window, dnd->will_accept,
+ want_position, rectangle.x, rectangle.y, rectangle.width, rectangle.height, dnd->supported_action);
+#endif
+ result = 1;
+ } else {
+ dnd_debug1 (" wrong stage or from wrong window");
+ }
+ } else if (xevent->xclient.message_type == dnd->XdndDrop) {
+#if XDND_VERSION >= 3
+ if (xevent->xany.window == dnd->dropper_toplevel && dnd->dropper_window)
+ xevent->xany.window = dnd->dropper_window;
+#endif
+ dnd_debug1 (" message_type is XdndDrop");
+ if (dnd->dragger_window == XDND_DROP_SOURCE_WIN (xevent) && dnd->stage == XDND_DROP_STAGE_ENTERED) {
+ dnd->time = CurrentTime;
+ if (dnd_version_at_least (dnd->dragging_version, 1))
+ dnd->time = XDND_DROP_TIME (xevent);
+ if (dnd->will_accept) {
+ dnd_debug1 (" will_accept is true - converting selectiong");
+ dnd_debug2 (" my window is %ld", dnd->dropper_window);
+ dnd_debug2 (" source window is %ld", dnd->dragger_window);
+ xdnd_convert_selection (dnd, dnd->dragger_window, dnd->dropper_window, dnd->desired_type);
+ dnd->stage = XDND_DROP_STAGE_CONVERTING;
+ } else {
+ dnd_debug1 (" will_accept is false - sending finished");
+ if (dnd_version_at_least (dnd->dragging_version, 2)) {
+#if XDND_VERSION >= 3
+ xdnd_send_finished (dnd, dnd->dragger_window, dnd->dropper_toplevel, 1);
+#else
+ xdnd_send_finished (dnd, dnd->dragger_window, xevent->xany.window, 1);
+#endif
+ }
+ xdnd_xfree (dnd->dragger_typelist);
+ xdnd_reset (dnd);
+ dnd->stage = XDND_DROP_STAGE_IDLE;
+ }
+ result = 1;
+ } else {
+ dnd_debug1 (" wrong stage or from wrong window");
+ }
+ }
+ }
+ return result;
+}
+
+/*
+ Following here is a sample implementation: Suppose we want a window
+ to recieve drops, but do not want to be concerned with setting up all
+ the DndClass methods. All we then do is call xdnd_get_drop() whenever a
+ ClientMessage is recieved. If the message has nothing to do with XDND,
+ xdnd_get_drop quickly returns 0. If it is a XdndEnter message, then
+ xdnd_get_drop enters its own XNextEvent loop and handles all XDND
+ protocol messages internally, returning the action requested.
+
+ You should pass a desired typelist and actionlist to xdnd_get_type.
+ These must be null terminated arrays of atoms, or a null pointer
+ if you would like any action or type to be accepted. If typelist
+ is null then the first type of the dragging widgets typelist will
+ be the one used. If actionlist is null, then only XdndActionCopy will
+ be accepted.
+
+ The result is stored in *data, length, type, x and y.
+ *data must be free'd.
+ */
+
+struct xdnd_get_drop_info {
+ unsigned char *drop_data;
+ int drop_data_length;
+ int x, y;
+ Atom return_type;
+ Atom return_action;
+ Atom *typelist;
+ Atom *actionlist;
+};
+
+static int widget_insert_drop (DndClass * dnd, unsigned char *data, int length, int remaining, Window into, Window from, Atom type)
+{
+ struct xdnd_get_drop_info *i;
+ (void) remaining;
+
(void) into;
+
(void) from;
+
(void) type;
+
+ i = (struct xdnd_get_drop_info *) dnd->user_hook1;
+ if (!i->drop_data) {
+ i->drop_data = malloc (length);
+ if (!i->drop_data)
+ return 1;
+ memcpy (i->drop_data, data, length);
+ i->drop_data_length = length;
+ } else {
+ unsigned char *t;
+ t = malloc (i->drop_data_length + length);
+ if (!t) {
+ free (i->drop_data);
+ i->drop_data = 0;
+ return 1;
+ }
+ memcpy (t, i->drop_data, i->drop_data_length);
+ memcpy (t + i->drop_data_length, data, length);
+ free (i->drop_data);
+ i->drop_data = t;
+ i->drop_data_length += length;
+ }
+ return 0;
+}
+
+static int widget_apply_position (DndClass * dnd, Window widgets_window, Window from,
+ Atom action, int x, int y, Time t, Atom * typelist,
+ int *want_position, Atom * supported_action_return, Atom * desired_type,
+ XRectangle * rectangle)
+{
+ int i, j;
+ struct xdnd_get_drop_info *info;
+ Atom *dropper_typelist, supported_type = 0;
+ Atom *supported_actions, supported_action = 0;
+
(void) widgets_window;
+
(void) from;
+
(void) t;
+
+ info = (struct xdnd_get_drop_info *) dnd->user_hook1;
+ dropper_typelist = info->typelist;
+ supported_actions = info->actionlist;
+
+ if (dropper_typelist) {
+/* find a correlation: */
+ for (j = 0; dropper_typelist[j]; j++) {
+ for (i = 0; typelist[i]; i++) {
+ if (typelist[i] == dropper_typelist[j]) {
+ supported_type = typelist[i];
+ break;
+ }
+ }
+ if (supported_type)
+ break;
+ }
+ } else {
+/* user did not specify, so return first type */
+ supported_type = typelist[0];
+ }
+/* not supported, so return false */
+ if (!supported_type)
+ return 0;
+
+ if (supported_actions) {
+ for (j = 0; supported_actions[j]; j++) {
+ if (action == supported_actions[j]) {
+ supported_action = action;
+ break;
+ }
+ }
+ } else {
+/* user did not specify */
+ if (action == dnd->XdndActionCopy)
+ supported_action = action;
+ }
+ if (!supported_action)
+ return 0;
+
+ *want_position = 1;
+ rectangle->x = rectangle->y = 0;
+ rectangle->width = rectangle->height = 0;
+
+ info->return_action = *supported_action_return = supported_action;
+ info->return_type = *desired_type = supported_type;
+ info->x = x;
+ info->y = y;
+
+ return 1;
+}
+
+Atom xdnd_get_drop (Display * display, XEvent * xevent, Atom * typelist, Atom * actionlist,
+ unsigned char **data, int *length, Atom * type, int *x, int *y)
+{
+ Atom action = 0;
+ static int initialised = 0;
+ static DndClass dnd;
+ if (!initialised) {
+ xdnd_init (&dnd, display);
+ initialised = 1;
+ }
+ if (xevent->type != ClientMessage || xevent->xclient.message_type != dnd.XdndEnter) {
+ return 0;
+ } else {
+ struct xdnd_get_drop_info i;
+
+/* setup user structure */
+ memset (&i, 0, sizeof (i));
+ i.actionlist = actionlist;
+ i.typelist = typelist;
+ dnd.user_hook1 = &i;
+
+/* setup methods */
+ dnd.widget_insert_drop = widget_insert_drop;
+ dnd.widget_apply_position = widget_apply_position;
+
+/* main loop */
+ for (;;) {
+ xdnd_handle_drop_events (&dnd, xevent);
+ if (dnd.stage == XDND_DROP_STAGE_IDLE)
+ break;
+ XNextEvent (dnd.display, xevent);
+ }
+
+/* return results */
+ if (i.drop_data) {
+ *length = i.drop_data_length;
+ *data = i.drop_data;
+ action = i.return_action;
+ *type = i.return_type;
+ *x = i.x;
+ *y = i.y;
+ }
+ }
+ return action;
+}
diff --git a/util/xdnd.h b/util/xdnd.h
new file mode 100644
index 00000000..53a2a8fb
--- /dev/null
+++ b/util/xdnd.h
@@ -0,0 +1,219 @@
+/* xdnd.c, xdnd.h - C program library for handling the Xdnd protocol
+ Copyright (C) 1996-2000 Paul Sheer
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ 02111-1307, USA.
+ */
+
+#ifndef _X_DND_H
+#define _X_DND_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* you can set this to either 2 (which support 0 and 1 as well) or 3 */
+/* #define XDND_VERSION 2 */
+
+#define XDND_VERSION 3
+
+/* XdndEnter */
+#define XDND_THREE 3
+#define XDND_ENTER_SOURCE_WIN(e)
((e)->xclient.data.l[0])
+#define XDND_ENTER_THREE_TYPES(e)
(((e)->xclient.data.l[1] & 0x1UL) == 0)
+#define XDND_ENTER_THREE_TYPES_SET(e,b)
(e)->xclient.data.l[1] = ((e)->xclient.data.l[1] & ~0x1UL) | (((b) == 0) ? 0 : 0x1UL)
+#define XDND_ENTER_VERSION(e)
((e)->xclient.data.l[1] >> 24)
+#define XDND_ENTER_VERSION_SET(e,v)
(e)->xclient.data.l[1] = ((e)->xclient.data.l[1] & ~(0xFF << 24)) | ((v) << 24)
+#define XDND_ENTER_TYPE(e,i)
((e)->xclient.data.l[2 + i])
/* i => (0, 1, 2) */
+
+/* XdndPosition */
+#define XDND_POSITION_SOURCE_WIN(e)
((e)->xclient.data.l[0])
+#define XDND_POSITION_ROOT_X(e)
((e)->xclient.data.l[2] >> 16)
+#define XDND_POSITION_ROOT_Y(e)
((e)->xclient.data.l[2] & 0xFFFFUL)
+#define XDND_POSITION_ROOT_SET(e,x,y)
(e)->xclient.data.l[2] = ((x) << 16) | ((y) & 0xFFFFUL)
+#define XDND_POSITION_TIME(e)
((e)->xclient.data.l[3])
+#define XDND_POSITION_ACTION(e)
((e)->xclient.data.l[4])
+
+/* XdndStatus */
+#define XDND_STATUS_TARGET_WIN(e)
((e)->xclient.data.l[0])
+#define XDND_STATUS_WILL_ACCEPT(e)
((e)->xclient.data.l[1] & 0x1L)
+#define XDND_STATUS_WILL_ACCEPT_SET(e,b) (e)->xclient.data.l[1] = ((e)->xclient.data.l[1] & ~0x1UL) | (((b) == 0) ? 0 : 0x1UL)
+#define XDND_STATUS_WANT_POSITION(e)
((e)->xclient.data.l[1] & 0x2UL)
+#define XDND_STATUS_WANT_POSITION_SET(e,b) (e)->xclient.data.l[1] = ((e)->xclient.data.l[1] & ~0x2UL) | (((b) == 0) ? 0 : 0x2UL)
+#define XDND_STATUS_RECT_X(e)
((e)->xclient.data.l[2] >> 16)
+#define XDND_STATUS_RECT_Y(e)
((e)->xclient.data.l[2] & 0xFFFFL)
+#define XDND_STATUS_RECT_WIDTH(e)
((e)->xclient.data.l[3] >> 16)
+#define XDND_STATUS_RECT_HEIGHT(e)
((e)->xclient.data.l[3] & 0xFFFFL)
+#define XDND_STATUS_RECT_SET(e,x,y,w,h)
{(e)->xclient.data.l[2] = ((x) << 16) | ((y) & 0xFFFFUL); (e)->xclient.data.l[3] = ((w) << 16) | ((h) & 0xFFFFUL); }
+#define XDND_STATUS_ACTION(e)
((e)->xclient.data.l[4])
+
+/* XdndLeave */
+#define XDND_LEAVE_SOURCE_WIN(e)
((e)->xclient.data.l[0])
+
+/* XdndDrop */
+#define XDND_DROP_SOURCE_WIN(e)
((e)->xclient.data.l[0])
+#define XDND_DROP_TIME(e)
((e)->xclient.data.l[2])
+
+/* XdndFinished */
+#define XDND_FINISHED_TARGET_WIN(e)
((e)->xclient.data.l[0])
+
+struct _DndCursor {
+ int width, height;
+ int x, y;
+ unsigned char *image_data, *mask_data;
+ char *_action;
+ Pixmap image_pixmap, mask_pixmap;
+ Cursor cursor;
+ Atom action;
+};
+
+typedef struct _DndCursor DndCursor;
+typedef struct _DndClass DndClass;
+
+struct _DndClass {
+/* insert chars sequentionally into the target widget, type will be the same as `desired_type'
+ returned from widget_apply_position. This may be called several times in succession
+ with sequention blocks of data. Must return non-zero on failure */
+ int (*widget_insert_drop) (DndClass * dnd, unsigned char *data, int length, int remaining, Window into, Window from, Atom type);
+
+/* In response to DELETE requests : FIXME - not yet used */
+ int (*widget_delete_selection) (DndClass * dnd, Window window, Window from);
+
+/* returns 1 if widget exists, zero otherwise. If this method is not
+ set then the code assumes that no widgets have support for recieving drops.
+ In this case none of the widget methods need be set. */
+ int (*widget_exists) (DndClass * dnd, Window window);
+
+/* must update the widgets border to its default appearance */
+ void (*widget_apply_leave) (DndClass * dnd, Window widgets_window);
+
+/* must update the widgets border to give the appearance of being able to recieve a drop,
+ plus return all data to pointers. As per the protocol, if the widget cannot
+ perform the action specified by `action' then it should return either XdndActionPrivate
+ or XdndActionCopy into supported_action (leaving 0 supported_action unchanged is equivalent
+ to XdndActionCopy). Returns 1 if ready to ok drop */
+ int (*widget_apply_position) (DndClass * dnd, Window widgets_window, Window from,
+ Atom action, int x, int y, Time t, Atom * typelist,
+ int *want_position, Atom * supported_action, Atom * desired_type,
+ XRectangle * rectangle);
+
+/* returns drag data of the specified type. This will be one of `typelist' given to xdnd_drag */
+ void (*widget_get_data) (DndClass * dnd, Window window, unsigned char **data, int *length, Atom type);
+
+/* this is called from with the main event loop if an expose event is recieved and is optional */
+ void (*handle_expose_events) (DndClass * dnd, XEvent * xevent);
+
+/* creates a chooser dialog if the action is XdndActionAsk. Returns non-zero on cancel */
+ int (*action_choose_dialog) (DndClass * dnd, char **descriptions, Atom * actions, Atom * result);
+
+#if 0 /* implemented internally */
+/* returns a widget that is dnd aware within a parent widget that lies under the point x, y */
+ Window (*widget_get_child_widget) (DndClass * dnd, Window parent, int x, int y);
+#endif
+
+ void *pad1[8];
+
+ DndCursor *cursors;
+
+ Display *display;
+
+ Atom XdndAware;
+ Atom XdndSelection;
+ Atom XdndEnter;
+ Atom XdndLeave;
+ Atom XdndPosition;
+ Atom XdndDrop;
+ Atom XdndFinished;
+ Atom XdndStatus;
+ Atom XdndActionCopy;
+ Atom XdndActionMove;
+ Atom XdndActionLink;
+ Atom XdndActionAsk;
+ Atom XdndActionPrivate;
+ Atom XdndTypeList;
+ Atom XdndActionList;
+ Atom XdndActionDescription;
+
+ Atom Xdnd_NON_PROTOCOL_ATOM;
+ Atom version;
+
+ Atom pad2[16];
+
+ Window root_window;
+
+#define XDND_DROP_STAGE_IDLE
0
+#define XDND_DRAG_STAGE_DRAGGING
1
+#define XDND_DRAG_STAGE_ENTERED
2
+#define XDND_DROP_STAGE_CONVERTING
3
+#define XDND_DROP_STAGE_ENTERED
4
+ int stage;
+ int dragging_version;
+ int internal_drag;
+ int want_position;
+ int ready_to_drop;
+ int will_accept;
+ XRectangle rectangle;
+ Window dropper_window, dragger_window;
+ Atom *dragger_typelist;
+ Atom desired_type;
+ Atom supported_action;
+ Time time;
+/* drop position from last XdndPosition */
+ int x, y;
+ int pad3[16];
+
+/* move euclidian pixels before considering this to be an actual drag */
+ float drag_threshold;
+
+/* block for only this many seconds on not receiving a XdndFinished from target, default : 10 */
+ int time_out;
+
+#define XDND_OPTION_NO_HYSTERESIS (1<<0)
+ int options;
+
+/* user hooks */
+ void *user_hook1;
+ void *user_hook2;
+ void *user_hook3;
+ Window dropper_toplevel;
+ void *pad4[15];
+};
+
+
+void xdnd_init (DndClass * dnd, Display * display);
+void xdnd_shut (DndClass * dnd);
+/* for nested widgets where parent and child receive drops of different
+types; then always pass typelist as null */
+void xdnd_set_dnd_aware (DndClass * dnd, Window window, Atom * typelist);
+int xdnd_is_dnd_aware (DndClass * dnd, Window window, int *version, Atom * typelist);
+void xdnd_set_type_list (DndClass * dnd, Window window, Atom * typelist);
+void xdnd_set_actions (DndClass * dnd, Window window, Atom * actions, char **descriptions);
+int xdnd_get_actions (DndClass * dnd, Window window, Atom ** actions, char ***descriptions);
+int xdnd_choose_action_dialog (DndClass * dnd, Atom * actions, char **descriptions, Atom * result);
+Atom xdnd_drag (DndClass * dnd, Window from, Atom action, Atom * typelist);
+
+/* Returns 1 if event is handled, This must be placed in the widget
+libraries main event loop and be called if the event type is
+ClientMessage or SelectionNotify */
+int xdnd_handle_drop_events (DndClass * dnd, XEvent * xevent);
+Atom xdnd_get_drop (Display * display, XEvent * xevent, Atom * typelist, Atom * actionlist,
+ unsigned char **data, int *length, Atom * type, int *x, int *y);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
/* !_X_DND_H */
--
2.43.0