This is an automated email generated because a ref change occurred in the
git repository for project wmaker-crm.git.
The branch, master has been updated
via 7e217857108a34c9ddfd6ed2ac60af58b6268615 (commit)
via 7cfdf6bd680cd5ce50715e540455a95e9a983ca9 (commit)
from 72942267aae0082e7816df73636d0c23a167fc28 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 7e217857108a34c9ddfd6ed2ac60af58b6268615
Author: David Maciejak <
david.m...@gmail.com>
Date: Sat, 17 Jan 2026 16:20:29 -0500
URL: <
https://repo.or.cz/wmaker-crm.git/7e217857108a34c9>
wmiv: add support for archive files and a new option to ignore unknown image format
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.ac
index ad67fc91e249..86ac2a008b42 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 924c317fc54e..0c66e90e7c92 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 0c14870294bf..ef9e586ec8a5 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 0d33a53539de..472905012723 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 000000000000..330db063c851
--- /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 000000000000..53a2a8fb248c
--- /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 */
commit 7cfdf6bd680cd5ce50715e540455a95e9a983ca9
Author: David Maciejak <
david.m...@gmail.com>
Date: Sat, 17 Jan 2026 16:09:37 -0500
URL: <
https://repo.or.cz/wmaker-crm.git/7cfdf6bd680cd5ce>
WRaster: add function to save image in memory
This patch adds the RSaveRawImage() function to the WRaster lib
to be able to save image structure in memory.
The WRaster lib version is bumped.
---
configure.ac | 4 +-
wrlib/ChangeLog | 2 +
wrlib/imgformat.h | 2 +
wrlib/load.c | 5 ++
wrlib/save.c | 21 ++++++-
wrlib/save_jpeg.c | 139 ++++++++++++++++++++++++++++++++++++++-------
wrlib/save_png.c | 126 ++++++++++++++++++++++++++++++++++------
wrlib/
wraster.h.in | 3 +
8 files changed, 263 insertions(+), 39 deletions(-)
diff --git a/
configure.ac b/
configure.ac
index e0f860a78945..ad67fc91e249 100644
--- a/
configure.ac
+++ b/
configure.ac
@@ -71,9 +71,9 @@ dnl 6. If any interfaces have been removed or changed since the last
dnl public release, then set age to 0.
dnl
dnl libwraster
-WRASTER_CURRENT=7
+WRASTER_CURRENT=8
WRASTER_REVISION=0
-WRASTER_AGE=1
+WRASTER_AGE=2
WRASTER_VERSION=$WRASTER_CURRENT:$WRASTER_REVISION:$WRASTER_AGE
AC_SUBST(WRASTER_VERSION)
dnl
diff --git a/wrlib/ChangeLog b/wrlib/ChangeLog
index d6d99681b23d..21e137847264 100644
--- a/wrlib/ChangeLog
+++ b/wrlib/ChangeLog
@@ -1,3 +1,5 @@
+- added RSaveRawImage()
+
- added RSaveTitledImage()
- removed obsoleted RDestroyImage()
diff --git a/wrlib/imgformat.h b/wrlib/imgformat.h
index 209b375f7bf4..a903bc3f667a 100644
--- a/wrlib/imgformat.h
+++ b/wrlib/imgformat.h
@@ -90,10 +90,12 @@ Bool RSaveXPM(RImage *image, const char *filename);
#ifdef USE_PNG
Bool RSavePNG(RImage *image, const char *filename, char *title);
+Bool RSaveRawPNG(RImage *image, char *title, unsigned char **out_buf, size_t *out_size);
#endif
#ifdef USE_JPEG
Bool RSaveJPEG(RImage *image, const char *filename, char *title);
+Bool RSaveRawJPEG(RImage *image, char *title, unsigned char **out_buf, size_t *out_size);
#endif
/*
diff --git a/wrlib/load.c b/wrlib/load.c
index ef4e3b94dc19..0f8242012404 100644
--- a/wrlib/load.c
+++ b/wrlib/load.c
@@ -162,6 +162,11 @@ RImage *RLoadImage(RContext *context, const char *file, int index)
assert(file != NULL);
+ /* just to suppress the compilation warning as index is only used with TIFF and GIF */
+#if !defined(USE_TIFF) && !defined(USE_GIF)
+ (void)index;
+#endif
+
if (RImageCacheSize < 0)
init_cache();
diff --git a/wrlib/save.c b/wrlib/save.c
index eee8ce54f6a1..840f002d613c 100644
--- a/wrlib/save.c
+++ b/wrlib/save.c
@@ -3,7 +3,7 @@
* Raster graphics library
*
* Copyright (c) 1998-2003 Alfredo K. Kojima
- * Copyright (c) 2013-2023 Window Maker Team
+ * Copyright (c) 2013-2025 Window Maker Team
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -40,6 +40,25 @@ Bool RSaveImage(RImage *image, const char *filename, const char *format)
return RSaveTitledImage(image, filename, format, NULL);
}
+Bool RSaveRawImage(RImage *image, const char *format, unsigned char **out_buf, size_t *out_size)
+{
+#ifdef USE_PNG
+ if (strcasecmp(format, "PNG") == 0)
+ return RSaveRawPNG(image, NULL, out_buf, out_size);
+#endif
+
+#ifdef USE_JPEG
+ if (strcasecmp(format, "JPG") == 0)
+ return RSaveRawJPEG(image, NULL, out_buf, out_size);
+
+ if (strcasecmp(format, "JPEG") == 0)
+ return RSaveRawJPEG(image, NULL, out_buf, out_size);
+#endif
+
+ RErrorCode = RERR_BADFORMAT;
+ return False;
+}
+
Bool RSaveTitledImage(RImage *image, const char *filename, const char *format, char *title)
{
#ifdef USE_PNG
diff --git a/wrlib/save_jpeg.c b/wrlib/save_jpeg.c
index 5320ba47bcdf..33a6d1782f3a 100644
--- a/wrlib/save_jpeg.c
+++ b/wrlib/save_jpeg.c
@@ -2,7 +2,7 @@
*
* Raster graphics library
*
- * Copyright (c) 2023 Window Maker Team
+ * Copyright (c) 2023-2025 Window Maker Team
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -33,39 +33,92 @@
#include "imgformat.h"
#include "wr_i18n.h"
+/* Structure for JPEG memory destination */
+struct jpeg_mem_data {
+ unsigned char **out_buf;
+ size_t *out_size;
+ unsigned char *buffer;
+ size_t buffer_size;
+};
+
+/* JPEG memory destination methods */
+static void jpeg_init_mem_destination(j_compress_ptr cinfo)
+{
+ struct jpeg_mem_data *dest = (struct jpeg_mem_data *)cinfo->client_data;
+ dest->buffer_size = 32768; /* Initial buffer size */
+ dest->buffer = malloc(dest->buffer_size);
+ if (!dest->buffer) {
+ /* Memory allocation failed - will be caught by caller */
+ dest->buffer_size = 0;
+ return;
+ }
+ cinfo->dest->next_output_byte = dest->buffer;
+ cinfo->dest->free_in_buffer = dest->buffer_size;
+}
+
+static boolean jpeg_empty_mem_output_buffer(j_compress_ptr cinfo)
+{
+ struct jpeg_mem_data *dest = (struct jpeg_mem_data *)cinfo->client_data;
+ size_t old_size = dest->buffer_size;
+ dest->buffer_size *= 2;
+ dest->buffer = realloc(dest->buffer, dest->buffer_size);
+ if (!dest->buffer) {
+ /* Memory allocation failed - signal error */
+ dest->buffer_size = 0;
+ return FALSE;
+ }
+ cinfo->dest->next_output_byte = dest->buffer + old_size;
+ cinfo->dest->free_in_buffer = dest->buffer_size - old_size;
+ return TRUE;
+}
+
+static void jpeg_term_mem_destination(j_compress_ptr cinfo)
+{
+ struct jpeg_mem_data *dest = (struct jpeg_mem_data *)cinfo->client_data;
+ *dest->out_size = dest->buffer_size - cinfo->dest->free_in_buffer;
+ *dest->out_buf = dest->buffer;
+ /* Don't free dest->buffer here - caller will free it */
+}
+
/*
- * Save RImage to JPEG image
+ * Save RImage to JPEG data in memory
*/
-Bool RSaveJPEG(RImage *img, const char *filename, char *title)
+Bool RSaveRawJPEG(RImage *img, char *title, unsigned char **out_buf, size_t *out_size)
{
- FILE *file;
- int x, y, img_depth;
+ int x, y;
char *buffer;
RColor pixel;
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
+ struct jpeg_destination_mgr jpeg_dest;
+ struct jpeg_mem_data mem_data;
JSAMPROW row_pointer;
- file = fopen(filename, "wb");
- if (!file) {
- RErrorCode = RERR_OPEN;
- return False;
- }
-
- if (img->format == RRGBAFormat)
- img_depth = 4;
- else
- img_depth = 3;
+ *out_buf = NULL;
+ *out_size = 0;
/* collect separate RGB values to a buffer */
buffer = malloc(sizeof(char) * 3 * img->width * img->height);
+ if (!buffer) {
+ RErrorCode = RERR_NOMEMORY;
+ return False;
+ }
+
for (y = 0; y < img->height; y++) {
for (x = 0; x < img->width; x++) {
RGetPixel(img, x, y, &pixel);
- buffer[y*img->width*3+x*3+0] = (char)(pixel.red);
- buffer[y*img->width*3+x*3+1] = (char)(pixel.green);
- buffer[y*img->width*3+x*3+2] = (char)(pixel.blue);
+ /* Handle transparent pixels by converting them to white
+ * since JPEG doesn't support transparency */
+ if (pixel.alpha == 0) {
+ buffer[y*img->width*3+x*3+0] = (char)255; /* white red */
+ buffer[y*img->width*3+x*3+1] = (char)255; /* white green */
+ buffer[y*img->width*3+x*3+2] = (char)255; /* white blue */
+ } else {
+ buffer[y*img->width*3+x*3+0] = (char)(pixel.red);
+ buffer[y*img->width*3+x*3+1] = (char)(pixel.green);
+ buffer[y*img->width*3+x*3+2] = (char)(pixel.blue);
+ }
}
}
@@ -74,7 +127,17 @@ Bool RSaveJPEG(RImage *img, const char *filename, char *title)
/* Initialize cinfo structure */
jpeg_create_compress(&cinfo);
- jpeg_stdio_dest(&cinfo, file);
+
+ /* Set up custom memory destination */
+ mem_data.out_buf = out_buf;
+ mem_data.out_size = out_size;
+ jpeg_dest.init_destination = jpeg_init_mem_destination;
+ jpeg_dest.empty_output_buffer = jpeg_empty_mem_output_buffer;
+ jpeg_dest.term_destination = jpeg_term_mem_destination;
+ cinfo.dest = &jpeg_dest;
+ cinfo.dest->next_output_byte = NULL;
+ cinfo.dest->free_in_buffer = 0;
+ cinfo.client_data = &mem_data;
cinfo.image_width = img->width;
cinfo.image_height = img->height;
@@ -90,15 +153,51 @@ Bool RSaveJPEG(RImage *img, const char *filename, char *title)
jpeg_write_marker(&cinfo, JPEG_COM, (const JOCTET*)title, strlen(title));
while (cinfo.next_scanline < cinfo.image_height) {
- row_pointer = (JSAMPROW) &buffer[cinfo.next_scanline * img_depth * img->width];
+ row_pointer = (JSAMPROW) &buffer[cinfo.next_scanline * 3 * img->width];
jpeg_write_scanlines(&cinfo, &row_pointer, 1);
}
jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
/* Clean */
free(buffer);
+
+ return True;
+}
+
+/*
+ * Save RImage to JPEG file
+ */
+
+Bool RSaveJPEG(RImage *img, const char *filename, char *title)
+{
+ FILE *file;
+ unsigned char *jpeg_data;
+ size_t jpeg_size;
+ size_t written;
+
+ /* Generate JPEG data in memory */
+ if (!RSaveRawJPEG(img, title, &jpeg_data, &jpeg_size)) {
+ return False;
+ }
+
+ /* Write to file */
+ file = fopen(filename, "wb");
+ if (!file) {
+ free(jpeg_data);
+ RErrorCode = RERR_OPEN;
+ return False;
+ }
+
+ written = fwrite(jpeg_data, 1, jpeg_size, file);
fclose(file);
+ free(jpeg_data);
+
+ if (written != jpeg_size) {
+ RErrorCode = RERR_WRITE;
+ return False;
+ }
return True;
}
diff --git a/wrlib/save_png.c b/wrlib/save_png.c
index 40d3b99603af..c8504118ba22 100644
--- a/wrlib/save_png.c
+++ b/wrlib/save_png.c
@@ -2,7 +2,7 @@
*
* Raster graphics library
*
- * Copyright (c) 2023 Window Maker Team
+ * Copyright (c) 2023-2025 Window Maker Team
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -33,12 +33,51 @@
#include "imgformat.h"
#include "wr_i18n.h"
+/* Structure to hold PNG data in memory */
+struct png_mem_data {
+ unsigned char *buffer;
+ size_t size;
+ size_t capacity;
+};
+
+/* Callback function to write PNG data to memory buffer */
+static void png_write_to_memory(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ struct png_mem_data *p = (struct png_mem_data *)png_get_io_ptr(png_ptr);
+ size_t new_size = p->size + length;
+
+ /* Expand buffer if necessary */
+ if (new_size > p->capacity) {
+ size_t new_capacity = p->capacity ? p->capacity * 2 : 8192;
+ while (new_capacity < new_size)
+ new_capacity *= 2;
+
+ unsigned char *new_buffer = realloc(p->buffer, new_capacity);
+ if (!new_buffer) {
+ png_error(png_ptr, "Out of memory");
+ return;
+ }
+ p->buffer = new_buffer;
+ p->capacity = new_capacity;
+ }
+
+ /* Copy data to buffer */
+ memcpy(p->buffer + p->size, data, length);
+ p->size += length;
+}
+
+/* Dummy flush function for memory I/O */
+static void png_flush_memory(png_structp png_ptr)
+{
+ /* No-op for memory I/O */
+ (void)png_ptr;
+}
+
/*
- * Save RImage to PNG image
+ * Save RImage to PNG data in memory
*/
-Bool RSavePNG(RImage *img, const char *filename, char *title)
+Bool RSaveRawPNG(RImage *img, char *title, unsigned char **out_buf, size_t *out_size)
{
- FILE *file;
png_structp png_ptr;
png_infop png_info_ptr;
png_bytep png_row;
@@ -46,17 +85,14 @@ Bool RSavePNG(RImage *img, const char *filename, char *title)
int x, y;
int width = img->width;
int height = img->height;
+ struct png_mem_data png_data = {NULL, 0, 0};
- file = fopen(filename, "wb");
- if (file == NULL) {
- RErrorCode = RERR_OPEN;
- return False;
- }
+ *out_buf = NULL;
+ *out_size = 0;
/* Initialize write structure */
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (png_ptr == NULL) {
- fclose(file);
RErrorCode = RERR_NOMEMORY;
return False;
}
@@ -64,19 +100,22 @@ Bool RSavePNG(RImage *img, const char *filename, char *title)
/* Initialize info structure */
png_info_ptr = png_create_info_struct(png_ptr);
if (png_info_ptr == NULL) {
- fclose(file);
+ png_destroy_write_struct(&png_ptr, NULL);
RErrorCode = RERR_NOMEMORY;
return False;
}
/* Setup Exception handling */
- if (setjmp(png_jmpbuf (png_ptr))) {
- fclose(file);
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ if (png_data.buffer)
+ free(png_data.buffer);
+ png_destroy_write_struct(&png_ptr, &png_info_ptr);
RErrorCode = RERR_INTERNAL;
return False;
}
- png_init_io(png_ptr, file);
+ /* Set up memory I/O */
+ png_set_write_fn(png_ptr, &png_data, png_write_to_memory, png_flush_memory);
/* Write header (8 bit colour depth) */
png_set_IHDR(png_ptr, png_info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB,
@@ -96,6 +135,13 @@ Bool RSavePNG(RImage *img, const char *filename, char *title)
/* Allocate memory for one row (3 bytes per pixel - RGB) */
png_row = (png_bytep) malloc(3 * width * sizeof(png_byte));
+ if (!png_row) {
+ if (png_data.buffer)
+ free(png_data.buffer);
+ png_destroy_write_struct(&png_ptr, &png_info_ptr);
+ RErrorCode = RERR_NOMEMORY;
+ return False;
+ }
/* Write image data */
for (y = 0; y < height; y++) {
@@ -114,8 +160,7 @@ Bool RSavePNG(RImage *img, const char *filename, char *title)
/* End write */
png_write_end(png_ptr, NULL);
- /* Clean */
- fclose(file);
+ /* Clean up structures */
if (png_info_ptr != NULL)
png_free_data(png_ptr, png_info_ptr, PNG_FREE_ALL, -1);
if (png_ptr != NULL)
@@ -123,5 +168,54 @@ Bool RSavePNG(RImage *img, const char *filename, char *title)
if (png_row != NULL)
free(png_row);
+ /* Return the buffer */
+ *out_buf = png_data.buffer;
+ *out_size = png_data.size;
+
+ return True;
+}
+
+/*
+ * Save RImage to PNG image
+ */
+Bool RSavePNG(RImage *img, const char *filename, char *title)
+{
+ FILE *file;
+ unsigned char *png_data = NULL;
+ size_t png_size = 0;
+ size_t written;
+
+ if (!img || !filename) {
+ RErrorCode = RERR_BADIMAGEFILE;
+ return False;
+ }
+
+ /* Use RSaveRawPNG to generate PNG data in memory */
+ if (!RSaveRawPNG(img, title, &png_data, &png_size)) {
+ /* Error code already set by RSaveRawPNG */
+ return False;
+ }
+
+ /* Open file for writing */
+ file = fopen(filename, "wb");
+ if (file == NULL) {
+ free(png_data);
+ RErrorCode = RERR_OPEN;
+ return False;
+ }
+
+ /* Write PNG data to file */
+ written = fwrite(png_data, 1, png_size, file);
+ fclose(file);
+
+ /* Check if all data was written */
+ if (written != png_size) {
+ free(png_data);
+ RErrorCode = RERR_WRITE;
+ return False;
+ }
+
+ /* Clean up */
+ free(png_data);
return True;
}
diff --git a/wrlib/
wraster.h.in b/wrlib/
wraster.h.in
index 6f6e5df4c5a8..6414455ee4e5 100644
--- a/wrlib/
wraster.h.in
+++ b/wrlib/
wraster.h.in
@@ -391,6 +391,9 @@ RImage *RGetImageFromXPMData(RContext *context, char **xpmData)
Bool RSaveImage(RImage *image, const char *filename, const char *format)
__wrlib_nonnull(1, 2, 3);
+Bool RSaveRawImage(RImage *image, const char *format, unsigned char **out_buf, size_t *out_size)
+ __wrlib_nonnull(1, 2, 3, 4);
+
Bool RSaveTitledImage(RImage *image, const char *filename, const char *format, char *title)
__wrlib_nonnull(1, 2, 3);
-----------------------------------------------------------------------
Summary of changes:
configure.ac | 20 +-
doc/wmiv.1 | 43 +-
util/Makefile.am | 5 +-
util/wmiv.c | 1767 +++++++++++++++++++++++++++++++++++++-------
util/xdnd.c | 1596 +++++++++++++++++++++++++++++++++++++++
util/xdnd.h | 219 ++++++
wrlib/ChangeLog | 2 +
wrlib/imgformat.h | 2 +
wrlib/load.c | 5 +
wrlib/save.c | 21 +-
wrlib/save_jpeg.c | 139 +++-
wrlib/save_png.c | 126 +++-
wrlib/
wraster.h.in | 3 +
13 files changed, 3639 insertions(+), 309 deletions(-)
create mode 100644 util/xdnd.c
create mode 100644 util/xdnd.h
repo.or.cz automatic notification. Contact project admin
crm...@gmail.com
if you want to unsubscribe, or site admin
ad...@repo.or.cz if you receive
no reply.
--
wmaker-crm.git ("The Window Maker window manager")