Author SHA1 Message Date
trurl 56e08a5966 Port WMPixmap to Rust. 2026-06-24 22:51:04 -04:00
trurl 6a4cb9977e Generate a distinct FFI module for constants in WINGs C headers. 2026-06-23 09:42:35 -04:00
trurl 0d5986bb89 Use a sed script instead of a shell script to fix up autogenerated WINGs FFI bindings. 2026-06-23 09:42:35 -04:00
trurl b7d40eeac7 Update the font panel screenshot to account for changes to font list.
This should be fixed someday to use a fixed set of built-in fonts. As-is, the font list uses whatever the test system has available.
2026-06-22 18:43:16 -04:00
trurl 6097395d45 Create integration tests for WINGs WMPixmap API. 2026-04-17 22:42:31 -04:00
trurl c527a9f007 Spotfix for import path error. 2026-04-14 15:00:49 -04:00
trurl b4a91efedc Port WMColor to Rust. 2026-04-14 13:35:09 -04:00
trurl 49e2a9071d Mirror WMRange into Rust.
This is a simple value type with only a constructor method. We keep duplicate type definitions in C and Rust for now, with the intention of doing something smarter later on.
2026-04-14 13:16:26 -04:00
trurl e71673e46b Get rid of the W_PIXEL macro. 2026-04-14 12:55:51 -04:00
trurl 386e3ca307 Create new wrlib-rs crate for Rust bindings to libwraster. 2026-04-13 16:18:55 -04:00
trurl 36e5eb1bde Delete Cargo.lock which shouldn't be tracked. 2026-04-13 16:18:55 -04:00
trurl 5784b9cbda Eliminate accidentally commited temporary file. 2026-04-12 10:05:02 -04:00
trurl a5908610e9 Fix false positive improper_ctypes warnings for structs that are opaque types from C. 2026-04-12 10:05:02 -04:00
trurl 18e54ecdfc Switch to cdylib for Rust crates.
This avoids duplicate symbol definitions when the same static lib sub-dependency is linked in from multiple direct dependencies.
2026-04-12 09:32:48 -04:00
trurl 01cb6645e0 Eliminate accidentally commited temporary file. 2026-04-12 09:32:48 -04:00
trurl 84510b84b4 Fix false positive improper_ctypes warnings for structs that are opaque types from C. 2026-04-12 09:32:48 -04:00
trurl 2da6de7593 Rewrite WMFontPanel in Rust. 2026-04-06 19:27:30 -04:00
trurl 6c17cc3a12 Add allow(non_snake_case) for WMList and WMButton FFI functions. 2026-04-06 19:27:30 -04:00
trurl c24a199243 Start porting the base WINGs widget type to Rust.
Porting WINGs widgets to Rust will require using some WINGs abstractions. One of them is the base widget type, which is implemented by giving all widgets a common layout (with the first two fields always being the same basic types).
2026-04-06 19:27:30 -04:00
trurl 1ea3c1b8db Reimplement the WMAddListItem macro in Rust.
This was originally a macro in C. We reimplement it as a Rust function.
2026-04-06 19:27:30 -04:00
trurl 39efd22bed Reimplment the WMCreateCommandButton macro in Rust.
This is a macro in C, and it would be helpful to be able to do the same thing in Rust. It is simply ported over as a Rust function.
2026-04-06 19:27:30 -04:00
trurl 52fc9eec43 Clean up bindgen invocation for WINGs somewhat.
This is still a mess, but it's not all in one big line now.

Going forward, commits that need new bindgen functions and types should put them on their own lines. That will make it easier to figure out which commit introduced the use of a given symbol.
2026-04-06 19:27:30 -04:00
trurl d2a42971c8 Name the anonymous enum for WMButton behaviors.
This is needed so that bindgen cann refer to the enum properly when generating a corresponding Rust type.
2026-04-06 19:27:30 -04:00
trurl d02d67ec61 Eliminate accidentally commited temporary file. 2026-04-06 19:27:30 -04:00
trurl b1eca604b2 Fix false positive improper_ctypes warnings for structs that are opaque types from C. 2026-04-06 19:27:30 -04:00
trurl 797ef1ace8 Fix false positive improper_ctypes warnings for structs that are opaque types from C (#27)
This should be simple enough not to need further review. It gets rid of a bunch of warnings about struct fields defined with Rust-only types which are not visible from C.

Reviewed-on: #27
2026-04-05 19:58:39 -04:00
trurl b5e94932c9 Update snapshot test of font panel rendering.
This accounts for a bugfix in array iteration, which had been causing font entries to be duplicated.
2026-03-31 11:20:46 -04:00
trurl efec1e8d1e Fix off-by-one error in WMArray iteration macro behavior 2026-03-31 11:17:36 -04:00
trurl 2aa0b0bf53 Create an integration test for the WMFontPanel widget. 2026-03-26 23:48:29 -04:00
trurl d4c8f986a6 Bump to public insta-image crate. 2026-03-26 23:47:15 -04:00
trurl ec1761f4f3 Utilities for X11 image capture and acceptance testing.
These are put in their own crate so that we can link tests against libwings freely.

Once libwings is entirely rewritten in Rust, we should be able to move these tests back into the wings-rs crate.
2026-03-26 16:59:23 -04:00
trurl 32cf67b012 Fix panic when a handler triggers handler scheduling. 2026-03-07 21:19:00 -05:00
trurl ee28e9d0a2 Drop debugging print that was committed by accident. 2026-03-02 12:22:22 -05:00
trurl e882fd1f13 Make WMCreateCommandButton into a proper function instead of a macro. 2026-03-02 12:18:21 -05:00
trurl eaabf7e20c Fix panic when notification dispatch triggers notification dispatch.
The global `NotificationCenter` is kept behind a mutex, which was locked when notifications were dispatched.

As a result, if notification dispatch triggered dispatch of other notifications, we would fail to unlock the mutex and panic.

This change copies dispatch instructions out from inside the mutex, releases the mutex, and then actually does the dispatch.
2026-03-02 12:12:25 -05:00
trurl 7c7b9aa97c Rewrite WINGs handlers.c in Rust. 2026-02-15 23:15:22 -05:00
trurl 223a64286f Remove WINGs input handlers.
Input handlers date back to early versions of WINGs. They provide
a way to listen for state changes on file descriptors (including
networking sockets) as part of the WINGs event loop.

This functionality is not actually used in the Window Maker source
tree. It may have been more useful in the past, when WINGs provided
a networking socket API. The status quo appears to be that this is
largely dead code. Pumping the X11 event queue is special-cased in
the input handling code, so it can stay in for now.
2026-02-12 12:53:14 -05:00
trurl ce97a3b39f Drop select(2)-based input FD polling.
This is an intermediate step in the process of dropping input FD
polling entirely (except for pumping the X11 event queue).

There is code to use poll(2) instead of select(2) to wait for events
on file descriptors (which includes waiting for events from the X11
event queue). It appears not to have been used in quite some time,
or perhaps never, as it contains a typo (`poll fd` instead of `pollfd`
on its first line).

To reduce code bloat and make source code easier to navigate, it
makes sense to prefer only one of these codepaths.

After correcting this typo, waiting for X11 events appears to work, so
the poll(2)-based code seems to be good enough. Keeping the poll-based
code is vaguely preferable because poll(2) has a slightly better
interface than select(2), and poll(2) has been a standard Unix API
for 30 years.

Since this code is slated to be replaced, the decision to use the
poll(2)-based code here is probably not very important. But future
programmers may want to use this as a branching-off point for switching
to epoll(7) or some other, less-select(2)-like interface.
2026-02-12 12:53:14 -05:00
trurl 5075c877fa Remove WMDeleteTimerWithClientData.
This function requires tracking an explict client data pointer, which it would be preferable not to have in the Rust rewrite.

Removing this function entails tracking timer handlers explicitly, but this is not an undue burden.
2026-02-12 12:53:14 -05:00
trurl fa67563c2b Break out Sendable into its own utility module. 2025-12-20 13:09:32 -05:00
trurl cd711ba52b Restore function prototype for W_ClearNotificationCenter.
This was mistakenly removed when writing wutil-rs/src/notification.rs.
2025-12-20 12:38:29 -05:00
trurl d46810291b Remove unused import. 2025-12-20 12:38:29 -05:00
trurl 7c875284dc Add build-generated files to .gitignore. 2025-12-20 12:35:10 -05:00
trurl 52db12fbf2 Merge pull request 'Merge refactor/wings-rs into refactor/riir' (#13) from refactor/wings-rs into refactor/riir
Reviewed-on: #13
2025-12-15 13:03:10 -05:00
trurl b2481cf657 Name arguments directly instead of aping the Google C++ style guide. 2025-12-15 13:00:00 -05:00
trurl c371e26d05 Add new Rust source files to Makefile.am.
Forgot to do this earlier.
2025-12-15 13:00:00 -05:00
trurl 98421afc38 Rewrite all functions from WINGs wfont.c in Rust.
This starts to establish the structure for an in-place rewrite of WINGs. An
actual redesign of WINGs may follow, but for now there are no plans to alter the
structure of WINGs substantially.
2025-12-15 13:00:00 -05:00
trurl 9e49ed98a2 Make WINGs configuration visible from Rust. 2025-12-15 13:00:00 -05:00
trurl a466f17c35 Fix build order issues by moving wings-rs/ into WINGs/.
Having wings-rs depend on WINGs/WINGs/WINGsP.h was not working very well if all
of WINGs/ had to be built before wings-rs/. Moving wings-rs/ into WINGs/
addresses this.
2025-12-15 13:00:00 -05:00
trurl d33c05ef08 Ignore target dir for nested Cargo subdirs, too. 2025-12-15 13:00:00 -05:00
trurl 79873413db Tweak build so that WINGsP.rs gets generated at the right time. 2025-12-15 13:00:00 -05:00
trurl 57ac6b8178 Put the font and y (ascent) fields of W_Font behind accessors. 2025-12-15 13:00:00 -05:00
trurl d30fe6182d Mark wings-rs as a staticlib. 2025-12-15 13:00:00 -05:00
trurl e20009a880 Use bindgen to generate Rust bindings for WINGs types.
We will use these to port W_Font, W_Screen, etc., to Rust.

Bindings are regenerated whenever the relevant C source files change and then
patched to make sure they will build. This is a bit hacky but will serve our
purposes: the struct definitions should be migrated to Rust sooner rather than
later, so the hackishness should be transient.
2025-12-15 13:00:00 -05:00
trurl ee71db6693 Remove unused functions WMSetWidgetDefaultFont, WMSetWidgetDefaultBoldFont. 2025-12-15 13:00:00 -05:00
trurl 01c7eb7275 Remove unused function WMCopyFontWithStyle. 2025-12-15 13:00:00 -05:00
trurl fa99c12fd7 Remove unused function WMIsAntialiasingEnabled. 2025-12-15 13:00:00 -05:00
trurl d7e815010b Merge pull request 'Merge refactor/wutil-rs into refactor/riir' (#12) from trurl/wmaker:refactor/wutil-rs into refactor/riir
Reviewed-on: #12
2025-12-15 12:50:52 -05:00
trurl a31fa582bd Merge branch 'refactor/riir' into refactor/wutil-rs 2025-12-15 12:49:49 -05:00
trurl e0fc92bf51 Fix omissions in WMHashEnumerator C interface.
The WMHashTable rewrite branch was prematurely merged. This patch fixes some of
the things that were overlooked in that merge. WMHashEnumerator should be an
opaque type that is free'd after it is created.

We hope to axe WMHashTable entirely, but it's reasonable to get this fix in for
now so that the WMFontPanel integration test in WINGs/Tests can run on top of
the changes we've made so far. As of this commit, it's still broken, but it
behaves better than it did.
2025-11-29 12:51:51 -05:00
trurl f8df6447ea Store addresses insead of pointers in WMArray.
Prior to this patch, when WINGs/Tests/wtest.c tries to build a FontPanel, it
crashes. This is because the rewritten WMSortArray passes pointers to array
items to its comparator function, but the original API passed pointers to
pointers to array items to its comparator function. This has been corrected, and
now WMSortArray should behave more like it originally did.

This patch also stores `usize` addresses instead of `NonNull` pointers because
some WMArray use sites actually store non-pointer data (by casting it to
uintptr_t or similar).
2025-11-29 12:51:51 -05:00
trurl bd18e0c600 add some hacky lines to start-captive-wmaker.sh to ease debugging of X11 integration from Emacs 2025-11-21 16:01:41 -05:00
trurl 50caed30c0 Add a wings-rs crate for porting WINGs proper to Rust. 2025-11-14 01:07:40 -05:00
158 changed files with 6905 additions and 2635 deletions
+19 -1
View File
@@ -83,13 +83,20 @@ WINGs/Tests/wtest
WPrefs.app/WPrefs
# These files are generated from make rules
wmlib/wmlib.pc
wrlib/wrlib.pc
WINGs/WINGs.pc
WINGs/WUtil.pc
doc/wmaker.1
doc/wmsetbg.1
wrlib/libwraster.map
WindowMaker/appearance.menu
WindowMaker/appearance.menu.fy
WindowMaker/appearance.menu.nl
WindowMaker/appearance.menu.sr
WindowMaker/menu
WindowMaker/menu.bg
WindowMaker/menu.fi
@@ -99,6 +106,7 @@ WindowMaker/menu.ko
WindowMaker/menu.nl
WindowMaker/menu.ro
WindowMaker/menu.sk
WindowMaker/menu.sr
WindowMaker/menu.zh_TW
WindowMaker/plmenu
WindowMaker/plmenu.bg
@@ -111,6 +119,7 @@ WindowMaker/plmenu.nl
WindowMaker/plmenu.pl
WindowMaker/plmenu.ro
WindowMaker/plmenu.sk
WindowMaker/plmenu.sr
WindowMaker/plmenu.zh_CN
WindowMaker/plmenu.zh_TW
WindowMaker/wmmacros
@@ -142,4 +151,13 @@ WPrefs.app/WPrefs.desktop
.pc
# Rust stuff.
/*/target/**
/**/target/**
WINGs/wings-rs-tests/Cargo.lock
WINGs/wings-rs/Cargo.lock
WINGs/wings-rs/src/WINGsP.rs
WINGs/wings-rs/src/WINGsP/constants.rs
WINGs/wings-rs/src/WINGsP/mod.rs
wmaker-rs/Cargo.lock
wrlib-rs/src/ffi.rs
wrlib-rs/Cargo.lock
wutil-rs/Cargo.lock
+1 -1
View File
@@ -39,7 +39,7 @@ ACLOCAL_AMFLAGS = -I m4
AM_DISTCHECK_CONFIGURE_FLAGS = --enable-silent-rules LINGUAS='*'
SUBDIRS = wrlib wutil-rs WINGs wmaker-rs src util po WindowMaker wmlib WPrefs.app doc
SUBDIRS = wrlib wrlib-rs wutil-rs WINGs wmaker-rs src util po WindowMaker wmlib WPrefs.app doc
DIST_SUBDIRS = $(SUBDIRS) test
EXTRA_DIST = TODO BUGS BUGFORM FAQ INSTALL \
+4 -12
View File
@@ -2,7 +2,7 @@
AUTOMAKE_OPTIONS =
SUBDIRS = WINGs . po Documentation Resources
SUBDIRS = WINGs wings-rs wings-rs-tests . po Documentation Resources
DIST_SUBDIRS = $(SUBDIRS) Tests Examples Extras
libWINGs_la_LDFLAGS = -version-info @WINGS_VERSION@
@@ -10,12 +10,10 @@ libWUtil_la_LDFLAGS = -version-info @WUTIL_VERSION@
lib_LTLIBRARIES = libWUtil.la libWINGs.la
wutilrs = $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a
wraster = $(top_builddir)/wrlib/libwraster.la
LDADD= libWUtil.la libWINGs.la $(wraster) $(wutilrs) @INTLIBS@
libWINGs_la_LIBADD = libWUtil.la $(wraster) $(wutilrs) @XLIBS@ @XFT_LIBS@ @FCLIBS@ @LIBM@ @PANGO_LIBS@
libWUtil_la_LIBADD = $(wutilrs)
LDADD= libWUtil.la libWINGs.la $(wraster) @INTLIBS@
libWINGs_la_LIBADD = libWUtil.la $(wraster) @XLIBS@ @XFT_LIBS@ @FCLIBS@ @LIBM@ @PANGO_LIBS@
EXTRA_DIST = BUGS make-rgb Examples Extras Tests
@@ -34,15 +32,12 @@ libWINGs_la_SOURCES = \
wbox.c \
wbrowser.c \
wbutton.c \
wcolor.c \
wcolorpanel.c \
wcolorwell.c \
wconfig.h \
wevent.c \
wfilepanel.c \
wframe.c \
wfont.c \
wfontpanel.c \
widgets.c \
winputmethod.c \
wlabel.c \
@@ -50,7 +45,6 @@ libWINGs_la_SOURCES = \
wmenuitem.c \
wmisc.c \
wpanel.c \
wpixmap.c \
wpopupbutton.c \
wprogressindicator.c \
wruler.c \
@@ -68,7 +62,6 @@ libWUtil_la_SOURCES = \
error.c \
error.h \
findfile.c \
handlers.c \
menuparser.c \
menuparser.h \
menuparser_macros.c \
@@ -78,8 +71,7 @@ libWUtil_la_SOURCES = \
userdefaults.h \
usleep.c \
wapplication.c \
wconfig.h \
wutil.c
wconfig.h
AM_CFLAGS = @PANGO_CFLAGS@
+6 -3
View File
@@ -4,14 +4,17 @@ AUTOMAKE_OPTIONS =
noinst_PROGRAMS = wtest wmquery wmfile testmywidget
LDADD= $(top_builddir)/WINGs/libWINGs.la $(top_builddir)/wrlib/libwraster.la \
LDADD= $(top_builddir)/WINGs/libWINGs.la \
$(top_builddir)/wrlib/libwraster.la \
$(top_builddir)/WINGs/libWUtil.la \
@XFT_LIBS@ @INTLIBS@ @XLIBS@
$(top_builddir)/WINGs/wings-rs/target/debug/libwings_rs.a \
@XFT_LIBS@ @INTLIBS@ @XLIBS@ @FCLIBS@ @PANGO_LIBS@
testmywidget_SOURCES = testmywidget.c mywidget.c mywidget.h
wtest_DEPENDENCIES = $(top_builddir)/WINGs/libWINGs.la
wtest_DEPENDENCIES = $(top_builddir)/WINGs/libWINGs.la \
$(top_builddir)/WINGs/wings-rs/target/debug/libwings_rs.a
EXTRA_DIST = logo.xpm upbtn.xpm wm.html wm.png
+25 -26
View File
@@ -132,7 +132,7 @@ typedef enum {
} WMButtonType;
/* button behaviour masks */
enum {
typedef enum {
WBBSpringLoadedMask = (1 << 0),
WBBPushInMask = (1 << 1),
WBBPushChangeMask = (1 << 2),
@@ -140,7 +140,7 @@ enum {
WBBStateLightMask = (1 << 5),
WBBStateChangeMask = (1 << 6),
WBBStatePushMask = (1 << 7)
};
} WMButtonBehaviorMask;
/* frame title positions */
@@ -688,8 +688,7 @@ void WMSetApplicationIconPixmap(WMScreen *app, WMPixmap *icon);
WMPixmap* WMGetApplicationIconPixmap(WMScreen *app);
/* If color==NULL it will use the default color for panels: ae/aa/ae */
WMPixmap* WMCreateApplicationIconBlendedPixmap(WMScreen *scr, const RColor *color);
WMPixmap* WMCreateApplicationIconGreyedPixmap(WMScreen *scr);
void WMSetApplicationIconWindow(WMScreen *scr, Window window);
@@ -778,12 +777,8 @@ void WMSetViewDragDestinationProcs(WMView *view, WMDragDestinationProcs *procs);
/* ---[ WINGs/wfont.c ]--------------------------------------------------- */
Bool WMIsAntialiasingEnabled(WMScreen *scrPtr);
WMFont* WMCreateFont(WMScreen *scrPtr, const char *fontName);
WMFont* WMCopyFontWithStyle(WMScreen *scrPtr, WMFont *font, WMFontStyle style);
WMFont* WMRetainFont(WMFont *font);
void WMReleaseFont(WMFont *font);
@@ -792,12 +787,10 @@ char* WMGetFontName(WMFont *font);
unsigned int WMFontHeight(WMFont *font);
unsigned int WMFontAscent(WMFont *font);
void WMGetScaleBaseFromSystemFont(WMScreen *scrPtr, int *alphabetWidth, int *fontHeight);
void WMSetWidgetDefaultFont(WMScreen *scr, WMFont *font);
void WMSetWidgetDefaultBoldFont(WMScreen *scr, WMFont *font);
WMFont* WMDefaultSystemFont(WMScreen *scrPtr);
WMFont* WMDefaultBoldSystemFont(WMScreen *scrPtr);
@@ -815,6 +808,8 @@ void WMDrawImageString(WMScreen *scr, Drawable d, WMColor *color,
int WMWidthOfString(WMFont *font, const char *text, int length);
struct _XftFont *WMFontXftFont(WMFont *font);
/* ---[ WINGs/wpixmap.c ]------------------------------------------------- */
WMPixmap* WMRetainPixmap(WMPixmap *pixmap);
@@ -837,26 +832,34 @@ WMSize WMGetPixmapSize(WMPixmap *pixmap);
WMPixmap* WMCreatePixmapFromFile(WMScreen *scrPtr, const char *fileName);
WMPixmap* WMCreateBlendedPixmapFromRImage(WMScreen *scrPtr, RImage *image,
const RColor *color);
WMPixmap* WMCreateGreyedPixmapFromRImage(WMScreen *scrPtr, RImage *image);
WMPixmap* WMCreateBlendedPixmapFromFile(WMScreen *scrPtr, const char *fileName,
const RColor *color);
WMPixmap* WMCreateGreyedPixmapFromFile(WMScreen *scrPtr, const char *fileName);
WMPixmap* WMCreateScaledBlendedPixmapFromFile(WMScreen *scrPtr, const char *fileName,
const RColor *color,
unsigned int width,
unsigned int height);
WMPixmap* WMCreateScaledPixmapFromFile(WMScreen *scrPtr, const char *fileName,
unsigned int width,
unsigned int height);
WMPixmap* WMCreateScaledGreyedPixmapFromFile(WMScreen *scrPtr, const char *fileName,
unsigned int width,
unsigned int height);
void WMDrawPixmap(WMPixmap *pixmap, Drawable d, int x, int y);
Pixmap WMGetPixmapXID(WMPixmap *pixmap);
/* Deliberately leaks the internal pixmap resource. */
void WMLeakPixmapXID(WMPixmap *pixmap);
Pixmap WMGetPixmapMaskXID(WMPixmap *pixmap);
void WMSetPixmapMaskXID(WMPixmap *pixmap, Pixmap mask);
int WMGetPixmapDepth(WMPixmap *pixmap);
WMPixmap* WMGetSystemPixmap(WMScreen *scr, int image);
/* ---[ WINGs/wcolor.c ]-------------------------------------------------- */
/* ---[ WINGs/wings-rs/src/color.rs ]------------------------------------- */
WMColor* WMDarkGrayColor(WMScreen *scr);
@@ -1043,11 +1046,7 @@ void WMCloseWindow(WMWindow *win);
void WMSetButtonAction(WMButton *bPtr, WMAction *action, void *clientData);
#define WMCreateCommandButton(parent) \
WMCreateCustomButton((parent), WBBSpringLoadedMask\
|WBBPushInMask\
|WBBPushLightMask\
|WBBPushChangeMask)
WMButton* WMCreateCommandButton(WMWidget *parent);
#define WMCreateRadioButton(parent) \
WMCreateButton((parent), WBTRadio)
+63 -105
View File
@@ -67,9 +67,11 @@ typedef struct W_DraggingInfo {
/* ---[ Structures from WINGs.h ]----------------------------------------- */
/* Pre-definition of internal structs */
/* Opaque primitive types defined in Rust. */
typedef struct W_Color W_Color;
typedef struct W_Pixmap W_Pixmap;
/* Pre-definition of internal structs */
typedef struct W_Pixmap WMPixmap;
typedef struct W_View W_View;
typedef struct W_FocusInfo {
@@ -104,7 +106,7 @@ typedef struct W_Screen {
W_FocusInfo *focusInfo;
RImage *applicationIconImage; /* image (can have alpha channel) */
W_Pixmap *applicationIconPixmap; /* pixmap - no alpha channel */
WMPixmap *applicationIconPixmap; /* pixmap - no alpha channel */
Window applicationIconWindow;
struct W_Window *windowList; /* list of windows in the app */
@@ -122,8 +124,6 @@ typedef struct W_Screen {
WMOpenPanel *sharedOpenPanel;
WMSavePanel *sharedSavePanel;
struct W_FontPanel *sharedFontPanel;
struct W_ColorPanel *sharedColorPanel;
Pixmap stipple;
@@ -132,10 +132,10 @@ typedef struct W_Screen {
W_DraggingInfo dragInfo;
/* colors */
W_Color *white;
W_Color *black;
W_Color *gray;
W_Color *darkGray;
WMColor *white;
WMColor *black;
WMColor *gray;
WMColor *darkGray;
GC stippleGC;
@@ -152,11 +152,11 @@ typedef struct W_Screen {
GC drawImStringGC; /* for WMDrawImageString() */
struct W_Font *normalFont;
WMFont *normalFont;
struct W_Font *boldFont;
WMFont *boldFont;
WMHashTable *fontCache;
void *fontCache; /* owned/maintainted by wings-rs/src/screen.rs */
Bool antialiasedText;
@@ -165,57 +165,57 @@ typedef struct W_Screen {
struct W_Balloon *balloon;
W_Pixmap *checkButtonImageOn;
W_Pixmap *checkButtonImageOff;
WMPixmap *checkButtonImageOn;
WMPixmap *checkButtonImageOff;
W_Pixmap *radioButtonImageOn;
W_Pixmap *radioButtonImageOff;
WMPixmap *radioButtonImageOn;
WMPixmap *radioButtonImageOff;
W_Pixmap *buttonArrow;
W_Pixmap *pushedButtonArrow;
WMPixmap *buttonArrow;
WMPixmap *pushedButtonArrow;
W_Pixmap *scrollerDimple;
WMPixmap *scrollerDimple;
W_Pixmap *upArrow;
W_Pixmap *downArrow;
W_Pixmap *leftArrow;
W_Pixmap *rightArrow;
WMPixmap *upArrow;
WMPixmap *downArrow;
WMPixmap *leftArrow;
WMPixmap *rightArrow;
W_Pixmap *hiUpArrow;
W_Pixmap *hiDownArrow;
W_Pixmap *hiLeftArrow;
W_Pixmap *hiRightArrow;
WMPixmap *hiUpArrow;
WMPixmap *hiDownArrow;
WMPixmap *hiLeftArrow;
WMPixmap *hiRightArrow;
W_Pixmap *pullDownIndicator;
W_Pixmap *popUpIndicator;
WMPixmap *pullDownIndicator;
WMPixmap *popUpIndicator;
W_Pixmap *checkMark;
WMPixmap *checkMark;
W_Pixmap *homeIcon;
W_Pixmap *altHomeIcon;
WMPixmap *homeIcon;
WMPixmap *altHomeIcon;
W_Pixmap *trashcanIcon;
W_Pixmap *altTrashcanIcon;
WMPixmap *trashcanIcon;
WMPixmap *altTrashcanIcon;
W_Pixmap *createDirIcon;
W_Pixmap *altCreateDirIcon;
WMPixmap *createDirIcon;
WMPixmap *altCreateDirIcon;
W_Pixmap *disketteIcon;
W_Pixmap *altDisketteIcon;
W_Pixmap *unmountIcon;
W_Pixmap *altUnmountIcon;
WMPixmap *disketteIcon;
WMPixmap *altDisketteIcon;
WMPixmap *unmountIcon;
WMPixmap *altUnmountIcon;
W_Pixmap *magnifyIcon;
/*W_Pixmap *altMagnifyIcon;*/
W_Pixmap *wheelIcon;
W_Pixmap *grayIcon;
W_Pixmap *rgbIcon;
W_Pixmap *cmykIcon;
W_Pixmap *hsbIcon;
W_Pixmap *customPaletteIcon;
W_Pixmap *colorListIcon;
WMPixmap *magnifyIcon;
/*WMPixmap *altMagnifyIcon;*/
WMPixmap *wheelIcon;
WMPixmap *grayIcon;
WMPixmap *rgbIcon;
WMPixmap *cmykIcon;
WMPixmap *hsbIcon;
WMPixmap *customPaletteIcon;
WMPixmap *colorListIcon;
W_Pixmap *defaultObjectIcon;
WMPixmap *defaultObjectIcon;
Cursor defaultCursor;
@@ -272,9 +272,9 @@ typedef struct W_Screen {
* Added at the end of the structure to avoid breaking binary compatibility
* with previous versions of the toolkit
*/
W_Pixmap *tristateButtonImageOn;
W_Pixmap *tristateButtonImageOff;
W_Pixmap *tristateButtonImageTri;
WMPixmap *tristateButtonImageOn;
WMPixmap *tristateButtonImageOff;
WMPixmap *tristateButtonImageTri;
} W_Screen;
@@ -369,8 +369,12 @@ Bool W_CheckIdleHandlers(void);
void W_CheckTimerHandlers(void);
Bool W_HandleInputEvents(Bool waitForInput, int inputfd);
/*
* Returns the duration in milliseconds until the next timer event should go off
* (saturating at INT_MAX). If there is no such timer event, returns a negative
* value.
*/
int W_DelayUntilNextTimerEvent_millis();
/* ---[ notification.c ]-------------------------------------------------- */
@@ -378,6 +382,8 @@ void W_InitNotificationCenter(void);
void W_ReleaseNotificationCenter(void);
void W_ClearNotificationCenter(void);
/* ---[ selection.c ]----------------------------------------------------- */
@@ -409,23 +415,6 @@ void W_BalloonHandleEnterView(WMView *view);
void W_BalloonHandleLeaveView(WMView *view);
/* ---[ wcolor.c ]-------------------------------------------------------- */
struct W_Color {
struct W_Screen *screen;
XColor color;
unsigned short alpha;
short refCount;
GC gc;
struct {
unsigned int exact:1;
} flags;
};
#define W_PIXEL(c) (c)->color.pixel
/* ---[ wevent.c ]-------------------------------------------------------- */
typedef struct W_EventHandler {
@@ -441,24 +430,6 @@ typedef struct W_EventHandler {
void W_CallDestroyHandlers(W_View *view);
/* ---[ wfont.c ]--------------------------------------------------------- */
typedef struct W_Font {
struct W_Screen *screen;
struct _XftFont *font;
short height;
short y;
short refCount;
char *name;
@USE_PANGO@ PangoLayout *layout;
} W_Font;
#define W_FONTID(f) (f)->font->fid
/* ---[ widgets.c ]------------------------------------------------------- */
#define WC_UserWidget 128
@@ -504,8 +475,8 @@ void W_DrawReliefWithGC(W_Screen *scr, Drawable d, int x, int y,
GC black, GC dark, GC light, GC white);
void W_PaintTextAndImage(W_View *view, int wrap, WMColor *textColor,
W_Font *font, WMReliefType relief, const char *text,
WMAlignment alignment, W_Pixmap *image,
WMFont *font, WMReliefType relief, const char *text,
WMAlignment alignment, WMPixmap *image,
WMImagePosition position, WMColor *backColor, int ofs);
void W_PaintText(W_View *view, Drawable d, WMFont *font, int x, int y,
@@ -515,19 +486,6 @@ void W_PaintText(W_View *view, Drawable d, WMFont *font, int x, int y,
int W_GetTextHeight(WMFont *font, const char *text, int width, int wrap);
/* ---[ wpixmap.c ]------------------------------------------------------- */
struct W_Pixmap {
struct W_Screen *screen;
Pixmap pixmap;
Pixmap mask;
unsigned short width;
unsigned short height;
short depth;
short refCount;
};
/* ---[ wview.c ]--------------------------------------------------------- */
typedef struct W_ViewDelegate {
+7 -26
View File
@@ -156,12 +156,7 @@ typedef struct {
/* DO NOT ACCESS THE CONTENTS OF THIS STRUCT */
typedef struct {
void *table;
void *nextItem;
int index;
} WMHashEnumerator;
typedef struct WMHashEnumerator WMHashEnumerator;
typedef struct {
@@ -286,7 +281,7 @@ char* wtrimspace(const char *s);
*/
char *wshellquote(const char *s);
/* ---[ WINGs/misc.c ]--------------------------------------------------- */
/* ---[ wutil-rs/src/range.rs ]------------------------------------------ */
WMRange wmkrange(int start, int count);
@@ -302,34 +297,20 @@ void wusleep(unsigned int usec);
/* Event handlers: timer, idle, input */
WMHandlerID WMAddTimerHandler(int milliseconds, WMCallback *callback,
WMHandlerID WMAddTimerHandler(unsigned milliseconds, WMCallback *callback,
void *cdata);
WMHandlerID WMAddPersistentTimerHandler(int milliseconds, WMCallback *callback,
WMHandlerID WMAddPersistentTimerHandler(unsigned milliseconds, WMCallback *callback,
void *cdata);
void WMDeleteTimerWithClientData(void *cdata);
void WMDeleteTimerHandler(WMHandlerID handlerID);
WMHandlerID WMAddIdleHandler(WMCallback *callback, void *cdata);
void WMDeleteIdleHandler(WMHandlerID handlerID);
WMHandlerID WMAddInputHandler(int fd, int condition, WMInputProc *proc,
void *clientData);
void WMDeleteInputHandler(WMHandlerID handlerID);
/* This function is used _only_ if you create a non-GUI program.
* For GUI based programs use WMNextEvent()/WMHandleEvent() instead.
* This function will handle all input/timer/idle events, then return.
*/
void WHandleEvents(void);
/* ---[ WINGs/hashtable.c ]----------------------------------------------- */
/* ---[ wutil-rs/src/hash_table.rs ]----------------------------------------------- */
WMHashTable* WMCreateIdentityHashTable();
@@ -361,7 +342,7 @@ void* WMHashInsert(WMHashTable *table, const void *key, const void *data);
void WMHashRemove(WMHashTable *table, const void *key);
/* warning: do not manipulate the table while using the enumerator functions */
WMHashEnumerator WMEnumerateHashTable(WMHashTable *table);
WMHashEnumerator* WMEnumerateHashTable(WMHashTable *table);
void* WMNextHashEnumeratorItem(WMHashEnumerator *enumerator);
@@ -376,7 +357,7 @@ Bool WMNextHashEnumeratorItemAndKey(WMHashEnumerator *enumerator,
void **item, void **key);
void WMFreeHashEnumerator(WMHashEnumerator *enumerator);
/* some predefined callback sets */
+3 -3
View File
@@ -195,7 +195,7 @@ static Window makeDragIcon(WMScreen * scr, WMPixmap * pixmap)
flags = CWSaveUnder | CWBackPixmap | CWOverrideRedirect | CWColormap;
attribs.save_under = True;
attribs.background_pixmap = pixmap->pixmap;
attribs.background_pixmap = WMGetPixmapXID(pixmap);
attribs.override_redirect = True;
attribs.colormap = scr->colormap;
@@ -203,8 +203,8 @@ static Window makeDragIcon(WMScreen * scr, WMPixmap * pixmap)
size.height, 0, scr->depth, InputOutput, scr->visual, flags, &attribs);
#ifdef USE_XSHAPE
if (pixmap->mask) {
XShapeCombineMask(scr->display, window, ShapeBounding, 0, 0, pixmap->mask, ShapeSet);
if (WMGetPixmapMaskXID(pixmap)) {
XShapeCombineMask(scr->display, window, ShapeBounding, 0, 0, WMGetPixmapMaskXID(pixmap), ShapeSet);
}
#endif
-554
View File
@@ -1,554 +0,0 @@
/*
* WINGs internal handlers: timer, idle and input handlers
*/
#include "wconfig.h"
#include "WINGsP.h"
#include <sys/types.h>
#include <unistd.h>
#include <X11/Xos.h>
#ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#include <time.h>
#ifndef X_GETTIMEOFDAY
#define X_GETTIMEOFDAY(t) gettimeofday(t, (struct timezone*)0)
#endif
typedef struct TimerHandler {
WMCallback *callback; /* procedure to call */
struct timeval when; /* when to call the callback */
void *clientData;
struct TimerHandler *next;
int nextDelay; /* 0 if it's one-shot */
} TimerHandler;
typedef struct IdleHandler {
WMCallback *callback;
void *clientData;
} IdleHandler;
typedef struct InputHandler {
WMInputProc *callback;
void *clientData;
int fd;
int mask;
} InputHandler;
/* queue of timer event handlers */
static TimerHandler *timerHandler = NULL;
static WMArray *idleHandler = NULL;
static WMArray *inputHandler = NULL;
#define timerPending() (timerHandler)
static void rightNow(struct timeval *tv)
{
X_GETTIMEOFDAY(tv);
}
/* is t1 after t2 ? */
#define IS_AFTER(t1, t2) (((t1).tv_sec > (t2).tv_sec) || \
(((t1).tv_sec == (t2).tv_sec) \
&& ((t1).tv_usec > (t2).tv_usec)))
#define IS_ZERO(tv) (tv.tv_sec == 0 && tv.tv_usec == 0)
#define SET_ZERO(tv) tv.tv_sec = 0, tv.tv_usec = 0
static void addmillisecs(struct timeval *tv, int milliseconds)
{
tv->tv_usec += milliseconds * 1000;
tv->tv_sec += tv->tv_usec / 1000000;
tv->tv_usec = tv->tv_usec % 1000000;
}
static void enqueueTimerHandler(TimerHandler * handler)
{
TimerHandler *tmp;
/* insert callback in queue, sorted by time left */
if (!timerHandler || !IS_AFTER(handler->when, timerHandler->when)) {
/* first in the queue */
handler->next = timerHandler;
timerHandler = handler;
} else {
tmp = timerHandler;
while (tmp->next && IS_AFTER(handler->when, tmp->next->when)) {
tmp = tmp->next;
}
handler->next = tmp->next;
tmp->next = handler;
}
}
static void delayUntilNextTimerEvent(struct timeval *delay)
{
struct timeval now;
TimerHandler *handler;
handler = timerHandler;
while (handler && IS_ZERO(handler->when))
handler = handler->next;
if (!handler) {
/* The return value of this function is only valid if there _are_
timers active. */
delay->tv_sec = 0;
delay->tv_usec = 0;
return;
}
rightNow(&now);
if (IS_AFTER(now, handler->when)) {
delay->tv_sec = 0;
delay->tv_usec = 0;
} else {
delay->tv_sec = handler->when.tv_sec - now.tv_sec;
delay->tv_usec = handler->when.tv_usec - now.tv_usec;
if (delay->tv_usec < 0) {
delay->tv_usec += 1000000;
delay->tv_sec--;
}
}
}
WMHandlerID WMAddTimerHandler(int milliseconds, WMCallback * callback, void *cdata)
{
TimerHandler *handler;
handler = wmalloc(sizeof(TimerHandler));
if (!handler)
return NULL;
rightNow(&handler->when);
addmillisecs(&handler->when, milliseconds);
handler->callback = callback;
handler->clientData = cdata;
handler->nextDelay = 0;
enqueueTimerHandler(handler);
return handler;
}
WMHandlerID WMAddPersistentTimerHandler(int milliseconds, WMCallback * callback, void *cdata)
{
TimerHandler *handler = WMAddTimerHandler(milliseconds, callback, cdata);
if (handler != NULL)
handler->nextDelay = milliseconds;
return handler;
}
void WMDeleteTimerWithClientData(void *cdata)
{
TimerHandler *handler, *tmp;
if (!cdata || !timerHandler)
return;
tmp = timerHandler;
if (tmp->clientData == cdata) {
tmp->nextDelay = 0;
if (!IS_ZERO(tmp->when)) {
timerHandler = tmp->next;
wfree(tmp);
}
} else {
while (tmp->next) {
if (tmp->next->clientData == cdata) {
handler = tmp->next;
handler->nextDelay = 0;
if (IS_ZERO(handler->when))
break;
tmp->next = handler->next;
wfree(handler);
break;
}
tmp = tmp->next;
}
}
}
void WMDeleteTimerHandler(WMHandlerID handlerID)
{
TimerHandler *tmp, *handler = (TimerHandler *) handlerID;
if (!handler || !timerHandler)
return;
tmp = timerHandler;
handler->nextDelay = 0;
if (IS_ZERO(handler->when))
return;
if (tmp == handler) {
timerHandler = handler->next;
wfree(handler);
} else {
while (tmp->next) {
if (tmp->next == handler) {
tmp->next = handler->next;
wfree(handler);
break;
}
tmp = tmp->next;
}
}
}
WMHandlerID WMAddIdleHandler(WMCallback * callback, void *cdata)
{
IdleHandler *handler;
handler = wmalloc(sizeof(IdleHandler));
if (!handler)
return NULL;
handler->callback = callback;
handler->clientData = cdata;
/* add handler at end of queue */
if (!idleHandler) {
idleHandler = WMCreateArrayWithDestructor(16, wfree);
}
WMAddToArray(idleHandler, handler);
return handler;
}
void WMDeleteIdleHandler(WMHandlerID handlerID)
{
IdleHandler *handler = (IdleHandler *) handlerID;
if (!handler || !idleHandler)
return;
WMRemoveFromArray(idleHandler, handler);
}
WMHandlerID WMAddInputHandler(int fd, int condition, WMInputProc * proc, void *clientData)
{
InputHandler *handler;
handler = wmalloc(sizeof(InputHandler));
handler->fd = fd;
handler->mask = condition;
handler->callback = proc;
handler->clientData = clientData;
if (!inputHandler)
inputHandler = WMCreateArrayWithDestructor(16, wfree);
WMAddToArray(inputHandler, handler);
return handler;
}
void WMDeleteInputHandler(WMHandlerID handlerID)
{
InputHandler *handler = (InputHandler *) handlerID;
if (!handler || !inputHandler)
return;
WMRemoveFromArray(inputHandler, handler);
}
Bool W_CheckIdleHandlers(void)
{
IdleHandler *handler;
WMArray *handlerCopy;
WMArrayIterator iter;
if (!idleHandler || WMGetArrayItemCount(idleHandler) == 0) {
/* make sure an observer in queue didn't added an idle handler */
return (idleHandler != NULL && WMGetArrayItemCount(idleHandler) > 0);
}
handlerCopy = WMCreateArrayWithArray(idleHandler);
WM_ITERATE_ARRAY(handlerCopy, handler, iter) {
/* check if the handler still exist or was removed by a callback */
if (WMGetFirstInArray(idleHandler, handler) == WANotFound)
continue;
(*handler->callback) (handler->clientData);
WMDeleteIdleHandler(handler);
}
WMFreeArray(handlerCopy);
/* this is not necesarrily False, because one handler can re-add itself */
return (WMGetArrayItemCount(idleHandler) > 0);
}
void W_CheckTimerHandlers(void)
{
TimerHandler *handler;
struct timeval now;
if (!timerHandler) {
return;
}
rightNow(&now);
handler = timerHandler;
while (handler && IS_AFTER(now, handler->when)) {
if (!IS_ZERO(handler->when)) {
SET_ZERO(handler->when);
(*handler->callback) (handler->clientData);
}
handler = handler->next;
}
while (timerHandler && IS_ZERO(timerHandler->when)) {
handler = timerHandler;
timerHandler = timerHandler->next;
if (handler->nextDelay > 0) {
handler->when = now;
addmillisecs(&handler->when, handler->nextDelay);
enqueueTimerHandler(handler);
} else {
wfree(handler);
}
}
}
/*
* This functions will handle input events on all registered file descriptors.
* Input:
* - waitForInput - True if we want the function to wait until an event
* appears on a file descriptor we watch, False if we
* want the function to immediately return if there is
* no data available on the file descriptors we watch.
* - inputfd - Extra input file descriptor to watch for input.
* This is only used when called from wevent.c to watch
* on ConnectionNumber(dpy) to avoid blocking of X events
* if we wait for input from other file handlers.
* Output:
* if waitForInput is False, the function will return False if there are no
* input handlers registered, or if there is no data
* available on the registered ones, and will return True
* if there is at least one input handler that has data
* available.
* if waitForInput is True, the function will return False if there are no
* input handlers registered, else it will block until an
* event appears on one of the file descriptors it watches
* and then it will return True.
*
* If the retured value is True, the input handlers for the corresponding file
* descriptors are also called.
*
* Parametersshould be passed like this:
* - from wevent.c:
* waitForInput - apropriate value passed by the function who called us
* inputfd = ConnectionNumber(dpy)
* - from wutil.c:
* waitForInput - apropriate value passed by the function who called us
* inputfd = -1
*
*/
Bool W_HandleInputEvents(Bool waitForInput, int inputfd)
{
#if defined(HAVE_POLL) && defined(HAVE_POLL_H) && !defined(HAVE_SELECT)
struct poll fd *fds;
InputHandler *handler;
int count, timeout, nfds, i, extrafd;
extrafd = (inputfd < 0) ? 0 : 1;
if (inputHandler)
nfds = WMGetArrayItemCount(inputHandler);
else
nfds = 0;
if (!extrafd && nfds == 0) {
return False;
}
fds = wmalloc((nfds + extrafd) * sizeof(struct pollfd));
if (extrafd) {
/* put this to the end of array to avoid using ranges from 1 to nfds+1 */
fds[nfds].fd = inputfd;
fds[nfds].events = POLLIN;
}
/* use WM_ITERATE_ARRAY() here */
for (i = 0; i < nfds; i++) {
handler = WMGetFromArray(inputHandler, i);
fds[i].fd = handler->fd;
fds[i].events = 0;
if (handler->mask & WIReadMask)
fds[i].events |= POLLIN;
if (handler->mask & WIWriteMask)
fds[i].events |= POLLOUT;
#if 0 /* FIXME */
if (handler->mask & WIExceptMask)
FD_SET(handler->fd, &eset);
#endif
}
/*
* Setup the timeout to the estimated time until the
* next timer expires.
*/
if (!waitForInput) {
timeout = 0;
} else if (timerPending()) {
struct timeval tv;
delayUntilNextTimerEvent(&tv);
timeout = tv.tv_sec * 1000 + tv.tv_usec / 1000;
} else {
timeout = -1;
}
count = poll(fds, nfds + extrafd, timeout);
if (count > 0 && nfds > 0) {
WMArray *handlerCopy = WMCreateArrayWithArray(inputHandler);
int mask;
/* use WM_ITERATE_ARRAY() here */
for (i = 0; i < nfds; i++) {
handler = WMGetFromArray(handlerCopy, i);
/* check if the handler still exist or was removed by a callback */
if (WMGetFirstInArray(inputHandler, handler) == WANotFound)
continue;
mask = 0;
if ((handler->mask & WIReadMask) &&
(fds[i].revents & (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)))
mask |= WIReadMask;
if ((handler->mask & WIWriteMask) && (fds[i].revents & (POLLOUT | POLLWRBAND)))
mask |= WIWriteMask;
if ((handler->mask & WIExceptMask) && (fds[i].revents & (POLLHUP | POLLNVAL | POLLERR)))
mask |= WIExceptMask;
if (mask != 0 && handler->callback) {
(*handler->callback) (handler->fd, mask, handler->clientData);
}
}
WMFreeArray(handlerCopy);
}
wfree(fds);
return (count > 0);
#else
#ifdef HAVE_SELECT
struct timeval timeout;
struct timeval *timeoutPtr;
fd_set rset, wset, eset;
int maxfd, nfds, i;
int count;
InputHandler *handler;
if (inputHandler)
nfds = WMGetArrayItemCount(inputHandler);
else
nfds = 0;
if (inputfd < 0 && nfds == 0) {
return False;
}
FD_ZERO(&rset);
FD_ZERO(&wset);
FD_ZERO(&eset);
if (inputfd < 0) {
maxfd = 0;
} else {
FD_SET(inputfd, &rset);
maxfd = inputfd;
}
/* use WM_ITERATE_ARRAY() here */
for (i = 0; i < nfds; i++) {
handler = WMGetFromArray(inputHandler, i);
if (handler->mask & WIReadMask)
FD_SET(handler->fd, &rset);
if (handler->mask & WIWriteMask)
FD_SET(handler->fd, &wset);
if (handler->mask & WIExceptMask)
FD_SET(handler->fd, &eset);
if (maxfd < handler->fd)
maxfd = handler->fd;
}
/*
* Setup the timeout to the estimated time until the
* next timer expires.
*/
if (!waitForInput) {
SET_ZERO(timeout);
timeoutPtr = &timeout;
} else if (timerPending()) {
delayUntilNextTimerEvent(&timeout);
timeoutPtr = &timeout;
} else {
timeoutPtr = (struct timeval *)0;
}
count = select(1 + maxfd, &rset, &wset, &eset, timeoutPtr);
if (count > 0 && nfds > 0) {
WMArray *handlerCopy = WMCreateArrayWithArray(inputHandler);
int mask;
/* use WM_ITERATE_ARRAY() here */
for (i = 0; i < nfds; i++) {
handler = WMGetFromArray(handlerCopy, i);
/* check if the handler still exist or was removed by a callback */
if (WMGetFirstInArray(inputHandler, handler) == WANotFound)
continue;
mask = 0;
if ((handler->mask & WIReadMask) && FD_ISSET(handler->fd, &rset))
mask |= WIReadMask;
if ((handler->mask & WIWriteMask) && FD_ISSET(handler->fd, &wset))
mask |= WIWriteMask;
if ((handler->mask & WIExceptMask) && FD_ISSET(handler->fd, &eset))
mask |= WIExceptMask;
if (mask != 0 && handler->callback) {
(*handler->callback) (handler->fd, mask, handler->clientData);
}
}
WMFreeArray(handlerCopy);
}
return (count > 0);
#else /* not HAVE_SELECT, not HAVE_POLL */
# error Neither select nor poll. You lose.
#endif /* HAVE_SELECT */
#endif /* HAVE_POLL */
}
-10
View File
@@ -24,16 +24,6 @@
#include "error.h"
WMRange wmkrange(int start, int count)
{
WMRange range;
range.position = start;
range.count = count;
return range;
}
/*
* wutil_shutdown - cleanup in WUtil when user program wants to exit
*/
+7 -17
View File
@@ -63,8 +63,8 @@ void WMSetApplicationIconPixmap(WMScreen * scr, WMPixmap * icon)
hints = XGetWMHints(scr->display, scr->groupLeader);
hints->flags |= IconPixmapHint | IconMaskHint;
hints->icon_pixmap = (icon != NULL ? icon->pixmap : None);
hints->icon_mask = (icon != NULL ? icon->mask : None);
hints->icon_pixmap = (icon != NULL ? WMGetPixmapXID(icon) : None);
hints->icon_mask = (icon != NULL ? WMGetPixmapMaskXID(icon) : None);
XSetWMHints(scr->display, scr->groupLeader, hints);
XFree(hints);
@@ -76,22 +76,12 @@ WMPixmap *WMGetApplicationIconPixmap(WMScreen * scr)
return scr->applicationIconPixmap;
}
WMPixmap *WMCreateApplicationIconBlendedPixmap(WMScreen * scr, const RColor * color)
WMPixmap *WMCreateApplicationIconGreyedPixmap(WMScreen * scr)
{
WMPixmap *pix;
if (scr->applicationIconImage) {
static const RColor gray = {
/* red */ 0xAE,
/* green */ 0xAA,
/* blue */ 0xAE,
/* alpha */ 0xFF
};
if (!color)
color = &gray;
pix = WMCreateBlendedPixmapFromRImage(scr, scr->applicationIconImage, color);
pix = WMCreateGreyedPixmapFromRImage(scr, scr->applicationIconImage);
} else {
pix = NULL;
}
@@ -133,10 +123,10 @@ void W_InitApplication(WMScreen * scr)
*/
if (scr->applicationIconPixmap) {
hints->flags |= IconPixmapHint;
hints->icon_pixmap = scr->applicationIconPixmap->pixmap;
if (scr->applicationIconPixmap->mask) {
hints->icon_pixmap = WMGetPixmapXID(scr->applicationIconPixmap);
if (WMGetPixmapMaskXID(scr->applicationIconPixmap)) {
hints->flags |= IconMaskHint;
hints->icon_mask = scr->applicationIconPixmap->mask;
hints->icon_mask = WMGetPixmapMaskXID(scr->applicationIconPixmap);
}
}
+4 -2
View File
@@ -456,12 +456,12 @@ static void handleEvents(XEvent * event, void *data)
static void destroyBalloon(Balloon * bPtr)
{
WMHashEnumerator e;
WMHashEnumerator *e;
char *str;
e = WMEnumerateHashTable(bPtr->table);
while ((str = WMNextHashEnumeratorItem(&e))) {
while ((str = WMNextHashEnumeratorItem(e))) {
wfree(str);
}
WMFreeHashTable(bPtr->table);
@@ -472,5 +472,7 @@ static void destroyBalloon(Balloon * bPtr)
if (bPtr->font)
WMReleaseFont(bPtr->font);
WMFreeHashEnumerator(e);
wfree(bPtr);
}
+28 -18
View File
@@ -15,11 +15,11 @@ typedef struct W_Button {
WMColor *altTextColor;
WMColor *disTextColor;
W_Pixmap *image;
W_Pixmap *altImage;
W_Pixmap *tsImage;
WMPixmap *image;
WMPixmap *altImage;
WMPixmap *tsImage;
W_Pixmap *dimage;
WMPixmap *dimage;
void *clientData;
WMAction *action;
@@ -216,6 +216,14 @@ WMButton *WMCreateButton(WMWidget * parent, WMButtonType type)
return bPtr;
}
WMButton *WMCreateCommandButton(WMWidget *parent)
{
return WMCreateCustomButton(
parent,
WBBSpringLoadedMask|WBBPushInMask|WBBPushLightMask|WBBPushChangeMask
);
}
static void updateDisabledMask(WMButton * bPtr)
{
WMScreen *scr = WMWidgetScreen(bPtr);
@@ -224,32 +232,32 @@ static void updateDisabledMask(WMButton * bPtr)
if (bPtr->image) {
XGCValues gcv;
if (bPtr->dimage->mask) {
XFreePixmap(dpy, bPtr->dimage->mask);
bPtr->dimage->mask = None;
if (WMGetPixmapMaskXID(bPtr->dimage)) {
XFreePixmap(dpy, WMGetPixmapMaskXID(bPtr->dimage));
WMSetPixmapMaskXID(bPtr->dimage, 0);
}
if (bPtr->flags.dimsWhenDisabled) {
bPtr->dimage->mask = XCreatePixmap(dpy, scr->stipple,
bPtr->dimage->width, bPtr->dimage->height, 1);
WMSize size = WMGetPixmapSize(bPtr->dimage);
WMSetPixmapMaskXID(bPtr->dimage, XCreatePixmap(dpy, scr->stipple, size.width, size.height, 1));
XSetForeground(dpy, scr->monoGC, 0);
XFillRectangle(dpy, bPtr->dimage->mask, scr->monoGC, 0, 0,
bPtr->dimage->width, bPtr->dimage->height);
XFillRectangle(dpy, WMGetPixmapMaskXID(bPtr->dimage), scr->monoGC, 0, 0,
size.width, size.height);
gcv.foreground = 1;
gcv.background = 0;
gcv.stipple = scr->stipple;
gcv.fill_style = FillStippled;
gcv.clip_mask = bPtr->image->mask;
gcv.clip_mask = WMGetPixmapMaskXID(bPtr->image);
gcv.clip_x_origin = 0;
gcv.clip_y_origin = 0;
XChangeGC(dpy, scr->monoGC, GCForeground | GCBackground | GCStipple
| GCFillStyle | GCClipMask | GCClipXOrigin | GCClipYOrigin, &gcv);
XFillRectangle(dpy, bPtr->dimage->mask, scr->monoGC, 0, 0,
bPtr->dimage->width, bPtr->dimage->height);
XFillRectangle(dpy, WMGetPixmapMaskXID(bPtr->dimage), scr->monoGC, 0, 0,
size.width, size.height);
gcv.fill_style = FillSolid;
gcv.clip_mask = None;
@@ -271,15 +279,17 @@ void WMSetButtonImage(WMButton * bPtr, WMPixmap * image)
bPtr->image = WMRetainPixmap(image);
if (bPtr->dimage) {
bPtr->dimage->pixmap = None;
WMLeakPixmapXID(bPtr->dimage);
WMReleasePixmap(bPtr->dimage);
bPtr->dimage = NULL;
}
if (image) {
WMSize size = WMGetPixmapSize(image);
bPtr->dimage = WMCreatePixmapFromXPixmaps(WMWidgetScreen(bPtr),
image->pixmap, None,
image->width, image->height, image->depth);
WMGetPixmapXID(image), None,
size.width, size.height,
WMGetPixmapDepth(image));
updateDisabledMask(bPtr);
}
@@ -763,7 +773,7 @@ static void destroyButton(Button * bPtr)
if (bPtr->dimage) {
/* yuck.. kluge */
bPtr->dimage->pixmap = None;
WMLeakPixmapXID(bPtr->dimage);
WMReleasePixmap(bPtr->dimage);
}
+16 -16
View File
@@ -560,7 +560,7 @@ static WMColorPanel *makeColorPanel(WMScreen * scrPtr, const char *name)
RReleaseImage(image);
if (pixmap)
W_PaintText(W_VIEW(panel->grayBrightnessS), pixmap->pixmap,
W_PaintText(W_VIEW(panel->grayBrightnessS), WMGetPixmapXID(pixmap),
panel->font12, 2, 0, 100, WALeft, scrPtr->white,
False, _("Brightness"), strlen(_("Brightness")));
else
@@ -581,7 +581,7 @@ static WMColorPanel *makeColorPanel(WMScreen * scrPtr, const char *name)
graybuttoncolor = WMCreateRGBColor(scrPtr, (255 / 6) * i << 8,
(255 / 6) * i << 8, (255 / 6) * i << 8, True);
WMPaintColorSwatch(graybuttoncolor, pixmap->pixmap, 0, 0, 15, 15);
WMPaintColorSwatch(graybuttoncolor, WMGetPixmapXID(pixmap), 0, 0, 15, 15);
WMReleaseColor(graybuttoncolor);
panel->grayPresetBtn[i] = WMCreateCommandButton(panel->grayFrm);
@@ -635,7 +635,7 @@ static WMColorPanel *makeColorPanel(WMScreen * scrPtr, const char *name)
RReleaseImage(image);
if (pixmap)
W_PaintText(W_VIEW(panel->rgbRedS), pixmap->pixmap, panel->font12,
W_PaintText(W_VIEW(panel->rgbRedS), WMGetPixmapXID(pixmap), panel->font12,
2, 0, 100, WALeft, scrPtr->white, False, _("Red"), strlen(_("Red")));
else
wwarning(_("Color Panel: Could not allocate memory"));
@@ -666,7 +666,7 @@ static WMColorPanel *makeColorPanel(WMScreen * scrPtr, const char *name)
RReleaseImage(image);
if (pixmap)
W_PaintText(W_VIEW(panel->rgbGreenS), pixmap->pixmap, panel->font12,
W_PaintText(W_VIEW(panel->rgbGreenS), WMGetPixmapXID(pixmap), panel->font12,
2, 0, 100, WALeft, scrPtr->white, False, _("Green"), strlen(_("Green")));
else
wwarning(_("Color Panel: Could not allocate memory"));
@@ -697,7 +697,7 @@ static WMColorPanel *makeColorPanel(WMScreen * scrPtr, const char *name)
RReleaseImage(image);
if (pixmap)
W_PaintText(W_VIEW(panel->rgbBlueS), pixmap->pixmap, panel->font12,
W_PaintText(W_VIEW(panel->rgbBlueS), WMGetPixmapXID(pixmap), panel->font12,
2, 0, 100, WALeft, scrPtr->white, False, _("Blue"), strlen(_("Blue")));
else
wwarning(_("Color Panel: Could not allocate memory"));
@@ -772,7 +772,7 @@ static WMColorPanel *makeColorPanel(WMScreen * scrPtr, const char *name)
RReleaseImage(image);
if (pixmap)
W_PaintText(W_VIEW(panel->cmykCyanS), pixmap->pixmap, panel->font12,
W_PaintText(W_VIEW(panel->cmykCyanS), WMGetPixmapXID(pixmap), panel->font12,
2, 0, 100, WALeft, scrPtr->black, False, _("Cyan"), strlen(_("Cyan")));
else
wwarning(_("Color Panel: Could not allocate memory"));
@@ -803,7 +803,7 @@ static WMColorPanel *makeColorPanel(WMScreen * scrPtr, const char *name)
RReleaseImage(image);
if (pixmap)
W_PaintText(W_VIEW(panel->cmykMagentaS), pixmap->pixmap, panel->font12,
W_PaintText(W_VIEW(panel->cmykMagentaS), WMGetPixmapXID(pixmap), panel->font12,
2, 0, 100, WALeft, scrPtr->black, False, _("Magenta"), strlen(_("Magenta")));
else
wwarning(_("Color Panel: Could not allocate memory"));
@@ -835,7 +835,7 @@ static WMColorPanel *makeColorPanel(WMScreen * scrPtr, const char *name)
RReleaseImage(image);
if (pixmap)
W_PaintText(W_VIEW(panel->cmykYellowS), pixmap->pixmap, panel->font12,
W_PaintText(W_VIEW(panel->cmykYellowS), WMGetPixmapXID(pixmap), panel->font12,
2, 0, 100, WALeft, scrPtr->black, False, _("Yellow"), strlen(_("Yellow")));
else
wwarning(_("Color Panel: Could not allocate memory"));
@@ -868,7 +868,7 @@ static WMColorPanel *makeColorPanel(WMScreen * scrPtr, const char *name)
RReleaseImage(image);
if (pixmap)
W_PaintText(W_VIEW(panel->cmykBlackS), pixmap->pixmap, panel->font12,
W_PaintText(W_VIEW(panel->cmykBlackS), WMGetPixmapXID(pixmap), panel->font12,
2, 0, 100, WALeft, scrPtr->black, False, _("Black"), strlen(_("Black")));
else
wwarning(_("Color Panel: Could not allocate memory"));
@@ -1292,7 +1292,7 @@ void WMSetColorPanelPickerMode(WMColorPanel * panel, WMColorPanelMode mode)
panel->mode = mode;
}
WMColor *WMGetColorPanelColor(WMColorPanel * panel)
WMColor* WMGetColorPanelColor(WMColorPanel * panel)
{
return WMGetColorWellColor(panel->colorWell);
}
@@ -1301,9 +1301,9 @@ void WMSetColorPanelColor(WMColorPanel * panel, WMColor * color)
{
WMSetColorWellColor(panel->colorWell, color);
panel->color.rgb.red = color->color.red >> 8;
panel->color.rgb.green = color->color.green >> 8;
panel->color.rgb.blue = color->color.blue >> 8;
panel->color.rgb.red = (WMRedComponentOfColor(color) >> 8);
panel->color.rgb.green = (WMGreenComponentOfColor(color) >> 8);
panel->color.rgb.blue = (WMBlueComponentOfColor(color) >> 8);
panel->color.set = cpRGB;
if (panel->mode == panel->lastChanged)
@@ -2711,7 +2711,7 @@ static void hsbUpdateBrightnessGradient(W_ColorPanel * panel)
RReleaseImage(sliderImg);
if (sliderPxmp)
W_PaintText(W_VIEW(panel->hsbBrightnessS), sliderPxmp->pixmap,
W_PaintText(W_VIEW(panel->hsbBrightnessS), WMGetPixmapXID(sliderPxmp),
panel->font12, 2, 0, 100, WALeft, scr->white,
False, _("Brightness"), strlen(_("Brightness")));
else
@@ -2744,7 +2744,7 @@ static void hsbUpdateSaturationGradient(W_ColorPanel * panel)
RReleaseImage(sliderImg);
if (sliderPxmp)
W_PaintText(W_VIEW(panel->hsbSaturationS), sliderPxmp->pixmap,
W_PaintText(W_VIEW(panel->hsbSaturationS), WMGetPixmapXID(sliderPxmp),
panel->font12, 2, 0, 100, WALeft,
from.hsv.value < 128 ? scr->white : scr->black, False,
_("Saturation"), strlen(_("Saturation")));
@@ -2779,7 +2779,7 @@ static void hsbUpdateHueGradient(W_ColorPanel * panel)
RReleaseImage(sliderImg);
if (sliderPxmp)
W_PaintText(W_VIEW(panel->hsbHueS), sliderPxmp->pixmap,
W_PaintText(W_VIEW(panel->hsbHueS), WMGetPixmapXID(sliderPxmp),
panel->font12, 2, 0, 100, WALeft,
hsvcolor.value < 128 ? scr->white : scr->black, False, _("Hue"), strlen(_("Hue")));
else
+24 -1
View File
@@ -3,8 +3,17 @@
* This event handling stuff was inspired on Tk.
*/
#include "wconfig.h"
#include "WINGsP.h"
#if !HAVE_POLL
# error poll(2) is not present on this system
#endif
#if HAVE_POLL_H
# include <poll.h>
#endif
/* table to map event types to event masks */
static const unsigned long eventMasks[] = {
0,
@@ -349,6 +358,9 @@ int WMIsDoubleClick(XEvent * event)
*/
static Bool waitForEvent(Display * dpy, unsigned long xeventmask, Bool waitForInput)
{
struct pollfd pfd;
int timeout, inputfd;
XSync(dpy, False);
if (xeventmask == 0) {
if (XPending(dpy))
@@ -361,7 +373,18 @@ static Bool waitForEvent(Display * dpy, unsigned long xeventmask, Bool waitForIn
}
}
return W_HandleInputEvents(waitForInput, ConnectionNumber(dpy));
inputfd = ConnectionNumber(dpy);
if (inputfd < 0) {
return False;
}
pfd.fd = inputfd;
pfd.events = POLLIN;
timeout = waitForInput ? W_DelayUntilNextTimerEvent_millis() : 0;
return poll(&pfd, 1, timeout) > 0;
}
void WMNextEvent(Display * dpy, XEvent * event)
+1 -1
View File
@@ -180,7 +180,7 @@ static WMFilePanel *makeFilePanel(WMScreen * scrPtr, const char *name, const cha
WMResizeWidget(fPtr->iconLabel, 64, 64);
WMMoveWidget(fPtr->iconLabel, 0, 0);
WMSetLabelImagePosition(fPtr->iconLabel, WIPImageOnly);
icon = WMCreateApplicationIconBlendedPixmap(scrPtr, (RColor *) NULL);
icon = WMCreateApplicationIconGreyedPixmap(scrPtr);
if (icon) {
WMSetLabelImage(fPtr->iconLabel, icon);
WMReleasePixmap(icon);
-433
View File
@@ -1,433 +0,0 @@
#include <stdlib.h>
#include "wconfig.h"
#include "WINGsP.h"
#include <wraster.h>
#include <assert.h>
#include <X11/Xlocale.h>
#include <X11/Xft/Xft.h>
#include <fontconfig/fontconfig.h>
#ifdef USE_PANGO
#include <pango/pango.h>
#include <pango/pangofc-fontmap.h>
#include <pango/pangoxft.h>
#endif
#define DEFAULT_FONT "sans serif:pixelsize=12"
#define DEFAULT_SIZE WINGsConfiguration.defaultFontSize
static FcPattern *xlfdToFcPattern(const char *xlfd)
{
FcPattern *pattern;
char *fname, *ptr;
/* Just skip old font names that contain %d in them.
* We don't support that anymore. */
if (strchr(xlfd, '%') != NULL)
return FcNameParse((FcChar8 *) DEFAULT_FONT);
fname = wstrdup(xlfd);
if ((ptr = strchr(fname, ','))) {
*ptr = 0;
}
pattern = XftXlfdParse(fname, False, False);
wfree(fname);
if (!pattern) {
wwarning(_("invalid font: %s. Trying '%s'"), xlfd, DEFAULT_FONT);
pattern = FcNameParse((FcChar8 *) DEFAULT_FONT);
}
return pattern;
}
static char *xlfdToFcName(const char *xlfd)
{
FcPattern *pattern;
char *fname;
char *result;
pattern = xlfdToFcPattern(xlfd);
fname = (char *)FcNameUnparse(pattern);
result = wstrdup(fname);
free(fname);
FcPatternDestroy(pattern);
return result;
}
static Bool hasProperty(FcPattern * pattern, const char *property)
{
FcValue val;
if (FcPatternGet(pattern, property, 0, &val) == FcResultMatch) {
return True;
}
return False;
}
static Bool hasPropertyWithStringValue(FcPattern * pattern, const char *object, const char *value)
{
FcChar8 *str;
int id;
if (!value || value[0] == 0)
return True;
id = 0;
while (FcPatternGetString(pattern, object, id, &str) == FcResultMatch) {
if (strcasecmp(value, (char *)str) == 0) {
return True;
}
id++;
}
return False;
}
static char *makeFontOfSize(const char *font, int size, const char *fallback)
{
FcPattern *pattern;
char *name;
char *result;
if (font[0] == '-') {
pattern = xlfdToFcPattern(font);
} else {
pattern = FcNameParse((const FcChar8 *) font);
}
/*FcPatternPrint(pattern); */
if (size > 0) {
FcPatternDel(pattern, FC_PIXEL_SIZE);
FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)size);
} else if (size == 0 && !hasProperty(pattern, "size") && !hasProperty(pattern, FC_PIXEL_SIZE)) {
FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)DEFAULT_SIZE);
}
if (fallback && !hasPropertyWithStringValue(pattern, FC_FAMILY, fallback)) {
FcPatternAddString(pattern, FC_FAMILY, (const FcChar8 *) fallback);
}
/*FcPatternPrint(pattern); */
name = (char *)FcNameUnparse(pattern);
result = wstrdup(name);
free(name);
FcPatternDestroy(pattern);
return result;
}
WMFont *WMCreateFont(WMScreen * scrPtr, const char *fontName)
{
Display *display = scrPtr->display;
WMFont *font;
char *fname;
#ifdef USE_PANGO
PangoFontMap *fontmap;
PangoContext *context;
PangoLayout *layout;
FcPattern *pattern;
PangoFontDescription *description;
double size;
#endif
if (fontName[0] == '-') {
fname = xlfdToFcName(fontName);
} else {
fname = wstrdup(fontName);
}
if (!WINGsConfiguration.antialiasedText && !strstr(fname, ":antialias=")) {
fname = wstrappend(fname, ":antialias=false");
}
font = WMHashGet(scrPtr->fontCache, fname);
if (font) {
WMRetainFont(font);
wfree(fname);
return font;
}
font = wmalloc(sizeof(WMFont));
font->screen = scrPtr;
font->font = XftFontOpenName(display, scrPtr->screen, fname);
if (!font->font) {
wfree(font);
wfree(fname);
return NULL;
}
font->height = font->font->ascent + font->font->descent;
font->y = font->font->ascent;
font->refCount = 1;
font->name = fname;
#ifdef USE_PANGO
fontmap = pango_xft_get_font_map(scrPtr->display, scrPtr->screen);
context = pango_font_map_create_context(fontmap);
layout = pango_layout_new(context);
pattern = FcNameParse((FcChar8 *) font->name);
description = pango_fc_font_description_from_pattern(pattern, FALSE);
/* Pango examines FC_SIZE but not FC_PIXEL_SIZE of the patten, but
* font-name has only "pixelsize", so set the size manually here.
*/
if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &size) == FcResultMatch)
pango_font_description_set_absolute_size(description, size * PANGO_SCALE);
pango_layout_set_font_description(layout, description);
font->layout = layout;
#endif
assert(WMHashInsert(scrPtr->fontCache, font->name, font) == NULL);
return font;
}
WMFont *WMRetainFont(WMFont * font)
{
wassertrv(font != NULL, NULL);
font->refCount++;
return font;
}
void WMReleaseFont(WMFont * font)
{
wassertr(font != NULL);
font->refCount--;
if (font->refCount < 1) {
XftFontClose(font->screen->display, font->font);
if (font->name) {
WMHashRemove(font->screen->fontCache, font->name);
wfree(font->name);
}
wfree(font);
}
}
Bool WMIsAntialiasingEnabled(WMScreen * scrPtr)
{
return scrPtr->antialiasedText;
}
unsigned int WMFontHeight(WMFont * font)
{
wassertrv(font != NULL, 0);
return font->height;
}
char *WMGetFontName(WMFont * font)
{
wassertrv(font != NULL, NULL);
return font->name;
}
void WMGetScaleBaseFromSystemFont(WMScreen *scrPtr, int *alphabetWidth, int *fontHeight)
{
WMFont *font;
font = WMDefaultSystemFont(scrPtr);
*alphabetWidth = WMWidthOfString(font, "abcdefghijklmnopqrstuvwxyz", 26);
*fontHeight = WMFontHeight(font);
WMReleaseFont(font);
}
WMFont *WMDefaultSystemFont(WMScreen * scrPtr)
{
return WMRetainFont(scrPtr->normalFont);
}
WMFont *WMDefaultBoldSystemFont(WMScreen * scrPtr)
{
return WMRetainFont(scrPtr->boldFont);
}
WMFont *WMSystemFontOfSize(WMScreen * scrPtr, int size)
{
WMFont *font;
char *fontSpec;
fontSpec = makeFontOfSize(WINGsConfiguration.systemFont, size, NULL);
font = WMCreateFont(scrPtr, fontSpec);
if (!font) {
wwarning(_("could not load font: %s."), fontSpec);
}
wfree(fontSpec);
return font;
}
WMFont *WMBoldSystemFontOfSize(WMScreen * scrPtr, int size)
{
WMFont *font;
char *fontSpec;
fontSpec = makeFontOfSize(WINGsConfiguration.boldSystemFont, size, NULL);
font = WMCreateFont(scrPtr, fontSpec);
if (!font) {
wwarning(_("could not load font: %s."), fontSpec);
}
wfree(fontSpec);
return font;
}
int WMWidthOfString(WMFont * font, const char *text, int length)
{
#ifdef USE_PANGO
const char *previous_text;
int width;
#else
XGlyphInfo extents;
#endif
wassertrv(font != NULL && text != NULL, 0);
#ifdef USE_PANGO
previous_text = pango_layout_get_text(font->layout);
if ((previous_text == NULL) || (strncmp(text, previous_text, length) != 0) || previous_text[length] != '\0')
pango_layout_set_text(font->layout, text, length);
pango_layout_get_pixel_size(font->layout, &width, NULL);
return width;
#else
XftTextExtentsUtf8(font->screen->display, font->font, (XftChar8 *) text, length, &extents);
return extents.xOff; /* don't ask :P */
#endif
}
void WMDrawString(WMScreen * scr, Drawable d, WMColor * color, WMFont * font, int x, int y, const char *text, int length)
{
XftColor xftcolor;
#ifdef USE_PANGO
const char *previous_text;
#endif
wassertr(font != NULL);
xftcolor.color.red = color->color.red;
xftcolor.color.green = color->color.green;
xftcolor.color.blue = color->color.blue;
xftcolor.color.alpha = color->alpha;;
xftcolor.pixel = W_PIXEL(color);
XftDrawChange(scr->xftdraw, d);
#ifdef USE_PANGO
previous_text = pango_layout_get_text(font->layout);
if ((previous_text == NULL) || (strcmp(text, previous_text) != 0))
pango_layout_set_text(font->layout, text, length);
pango_xft_render_layout(scr->xftdraw, &xftcolor, font->layout, x * PANGO_SCALE, y * PANGO_SCALE);
#else
XftDrawStringUtf8(scr->xftdraw, &xftcolor, font->font, x, y + font->y, (XftChar8 *) text, length);
#endif
}
void
WMDrawImageString(WMScreen * scr, Drawable d, WMColor * color, WMColor * background,
WMFont * font, int x, int y, const char *text, int length)
{
XftColor textColor;
XftColor bgColor;
#ifdef USE_PANGO
const char *previous_text;
#endif
wassertr(font != NULL);
textColor.color.red = color->color.red;
textColor.color.green = color->color.green;
textColor.color.blue = color->color.blue;
textColor.color.alpha = color->alpha;;
textColor.pixel = W_PIXEL(color);
bgColor.color.red = background->color.red;
bgColor.color.green = background->color.green;
bgColor.color.blue = background->color.blue;
bgColor.color.alpha = background->alpha;;
bgColor.pixel = W_PIXEL(background);
XftDrawChange(scr->xftdraw, d);
XftDrawRect(scr->xftdraw, &bgColor, x, y, WMWidthOfString(font, text, length), font->height);
#ifdef USE_PANGO
previous_text = pango_layout_get_text(font->layout);
if ((previous_text == NULL) || (strcmp(text, previous_text) != 0))
pango_layout_set_text(font->layout, text, length);
pango_xft_render_layout(scr->xftdraw, &textColor, font->layout, x * PANGO_SCALE, y * PANGO_SCALE);
#else
XftDrawStringUtf8(scr->xftdraw, &textColor, font->font, x, y + font->y, (XftChar8 *) text, length);
#endif
}
WMFont *WMCopyFontWithStyle(WMScreen * scrPtr, WMFont * font, WMFontStyle style)
{
FcPattern *pattern;
WMFont *copy;
char *name;
if (!font)
return NULL;
/* It's enough to add italic to slant, even if the font has no italic
* variant, but only oblique. This is because fontconfig will actually
* return the closest match font to what we requested which is the
* oblique font. Same goes for using bold for weight.
*/
pattern = FcNameParse((FcChar8 *) WMGetFontName(font));
switch (style) {
case WFSNormal:
FcPatternDel(pattern, FC_WEIGHT);
FcPatternDel(pattern, FC_SLANT);
break;
case WFSBold:
FcPatternDel(pattern, FC_WEIGHT);
FcPatternAddString(pattern, FC_WEIGHT, (FcChar8 *) "bold");
break;
case WFSItalic:
FcPatternDel(pattern, FC_SLANT);
FcPatternAddString(pattern, FC_SLANT, (FcChar8 *) "italic");
break;
case WFSBoldItalic:
FcPatternDel(pattern, FC_WEIGHT);
FcPatternDel(pattern, FC_SLANT);
FcPatternAddString(pattern, FC_WEIGHT, (FcChar8 *) "bold");
FcPatternAddString(pattern, FC_SLANT, (FcChar8 *) "italic");
break;
}
name = (char *)FcNameUnparse(pattern);
copy = WMCreateFont(scrPtr, name);
FcPatternDestroy(pattern);
free(name);
return copy;
}
-825
View File
@@ -1,825 +0,0 @@
#include "WINGsP.h"
#include "WUtil.h"
#include "wconfig.h"
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <stdint.h>
#include <X11/Xft/Xft.h>
#include <fontconfig/fontconfig.h>
/* XXX TODO */
char *WMFontPanelFontChangedNotification = "WMFontPanelFontChangedNotification";
typedef struct W_FontPanel {
WMWindow *win;
WMFrame *upperF;
WMTextField *sampleT;
WMSplitView *split;
WMFrame *lowerF;
WMLabel *famL;
WMList *famLs;
WMLabel *typL;
WMList *typLs;
WMLabel *sizL;
WMTextField *sizT;
WMList *sizLs;
WMAction2 *action;
void *data;
WMButton *revertB;
WMButton *setB;
WMPropList *fdb;
} FontPanel;
#define MIN_UPPER_HEIGHT 20
#define MIN_LOWER_HEIGHT 140
#define BUTTON_SPACE_HEIGHT 40
#define MIN_WIDTH 250
#define MIN_HEIGHT (MIN_UPPER_HEIGHT+MIN_LOWER_HEIGHT+BUTTON_SPACE_HEIGHT)
#define DEF_UPPER_HEIGHT 60
#define DEF_LOWER_HEIGHT 310
#define DEF_WIDTH 320
#define DEF_HEIGHT (DEF_UPPER_HEIGHT+DEF_LOWER_HEIGHT)
static const int scalableFontSizes[] = {
8,
10,
11,
12,
14,
16,
18,
20,
24,
36,
48,
64
};
static void setFontPanelFontName(FontPanel * panel, const char *family, const char *style, double size);
static int isXLFD(const char *font, int *length_ret);
static void arrangeLowerFrame(FontPanel * panel);
static void familyClick(WMWidget *, void *);
static void typefaceClick(WMWidget *, void *);
static void sizeClick(WMWidget *, void *);
static void listFamilies(WMScreen * scr, WMFontPanel * panel);
static void splitViewConstrainCallback(WMSplitView * sPtr, int indView, int *min, int *max)
{
/* Parameter not used, but tell the compiler that it is ok */
(void) sPtr;
(void) max;
if (indView == 0)
*min = MIN_UPPER_HEIGHT;
else
*min = MIN_LOWER_HEIGHT;
}
static void notificationObserver(void *self, WMNotification * notif)
{
WMFontPanel *panel = (WMFontPanel *) self;
void *object = WMGetNotificationObject(notif);
if (WMGetNotificationName(notif) == WMViewSizeDidChangeNotification) {
if (object == WMWidgetView(panel->win)) {
int h = WMWidgetHeight(panel->win);
int w = WMWidgetWidth(panel->win);
WMResizeWidget(panel->split, w, h - BUTTON_SPACE_HEIGHT);
WMMoveWidget(panel->setB, w - 80, h - (BUTTON_SPACE_HEIGHT - 5));
WMMoveWidget(panel->revertB, w - 240, h - (BUTTON_SPACE_HEIGHT - 5));
} else if (object == WMWidgetView(panel->upperF)) {
if (WMWidgetHeight(panel->upperF) < MIN_UPPER_HEIGHT) {
WMResizeWidget(panel->upperF, WMWidgetWidth(panel->upperF), MIN_UPPER_HEIGHT);
} else {
WMResizeWidget(panel->sampleT, WMWidgetWidth(panel->upperF) - 20,
WMWidgetHeight(panel->upperF) - 10);
}
} else if (object == WMWidgetView(panel->lowerF)) {
if (WMWidgetHeight(panel->lowerF) < MIN_LOWER_HEIGHT) {
WMResizeWidget(panel->upperF, WMWidgetWidth(panel->upperF), MIN_UPPER_HEIGHT);
WMMoveWidget(panel->lowerF, 0, WMWidgetHeight(panel->upperF)
+ WMGetSplitViewDividerThickness(panel->split));
WMResizeWidget(panel->lowerF, WMWidgetWidth(panel->lowerF),
WMWidgetWidth(panel->split) - MIN_UPPER_HEIGHT
- WMGetSplitViewDividerThickness(panel->split));
} else {
arrangeLowerFrame(panel);
}
}
}
}
static void closeWindow(WMWidget * w, void *data)
{
FontPanel *panel = (FontPanel *) data;
/* Parameter not used, but tell the compiler that it is ok */
(void) w;
WMHideFontPanel(panel);
}
static void setClickedAction(WMWidget * w, void *data)
{
FontPanel *panel = (FontPanel *) data;
/* Parameter not used, but tell the compiler that it is ok */
(void) w;
if (panel->action)
(*panel->action) (panel, panel->data);
}
static void revertClickedAction(WMWidget * w, void *data)
{
/* Parameter not used, but tell the compiler that it is ok */
(void) w;
(void) data;
/*FontPanel *panel = (FontPanel*)data; */
/* XXX TODO */
}
WMFontPanel *WMGetFontPanel(WMScreen * scr)
{
FontPanel *panel;
WMColor *dark, *white;
WMFont *font;
int divThickness;
if (scr->sharedFontPanel)
return scr->sharedFontPanel;
panel = wmalloc(sizeof(FontPanel));
panel->win = WMCreateWindow(scr, "fontPanel");
/* WMSetWidgetBackgroundColor(panel->win, WMWhiteColor(scr)); */
WMSetWindowTitle(panel->win, _("Font Panel"));
WMResizeWidget(panel->win, DEF_WIDTH, DEF_HEIGHT);
WMSetWindowMinSize(panel->win, MIN_WIDTH, MIN_HEIGHT);
WMSetViewNotifySizeChanges(WMWidgetView(panel->win), True);
WMSetWindowCloseAction(panel->win, closeWindow, panel);
panel->split = WMCreateSplitView(panel->win);
WMResizeWidget(panel->split, DEF_WIDTH, DEF_HEIGHT - BUTTON_SPACE_HEIGHT);
WMSetSplitViewConstrainProc(panel->split, splitViewConstrainCallback);
divThickness = WMGetSplitViewDividerThickness(panel->split);
panel->upperF = WMCreateFrame(panel->win);
WMSetFrameRelief(panel->upperF, WRFlat);
WMSetViewNotifySizeChanges(WMWidgetView(panel->upperF), True);
panel->lowerF = WMCreateFrame(panel->win);
/* WMSetWidgetBackgroundColor(panel->lowerF, WMBlackColor(scr)); */
WMSetFrameRelief(panel->lowerF, WRFlat);
WMSetViewNotifySizeChanges(WMWidgetView(panel->lowerF), True);
WMAddSplitViewSubview(panel->split, W_VIEW(panel->upperF));
WMAddSplitViewSubview(panel->split, W_VIEW(panel->lowerF));
WMResizeWidget(panel->upperF, DEF_WIDTH, DEF_UPPER_HEIGHT);
WMResizeWidget(panel->lowerF, DEF_WIDTH, DEF_LOWER_HEIGHT);
WMMoveWidget(panel->lowerF, 0, 60 + divThickness);
white = WMWhiteColor(scr);
dark = WMDarkGrayColor(scr);
panel->sampleT = WMCreateTextField(panel->upperF);
WMResizeWidget(panel->sampleT, DEF_WIDTH - 20, 50);
WMMoveWidget(panel->sampleT, 10, 10);
WMSetTextFieldText(panel->sampleT, _("The quick brown fox jumps over the lazy dog"));
font = WMBoldSystemFontOfSize(scr, 12);
panel->famL = WMCreateLabel(panel->lowerF);
WMSetWidgetBackgroundColor(panel->famL, dark);
WMSetLabelText(panel->famL, _("Family"));
WMSetLabelFont(panel->famL, font);
WMSetLabelTextColor(panel->famL, white);
WMSetLabelRelief(panel->famL, WRSunken);
WMSetLabelTextAlignment(panel->famL, WACenter);
panel->famLs = WMCreateList(panel->lowerF);
WMSetListAction(panel->famLs, familyClick, panel);
panel->typL = WMCreateLabel(panel->lowerF);
WMSetWidgetBackgroundColor(panel->typL, dark);
WMSetLabelText(panel->typL, _("Typeface"));
WMSetLabelFont(panel->typL, font);
WMSetLabelTextColor(panel->typL, white);
WMSetLabelRelief(panel->typL, WRSunken);
WMSetLabelTextAlignment(panel->typL, WACenter);
panel->typLs = WMCreateList(panel->lowerF);
WMSetListAction(panel->typLs, typefaceClick, panel);
panel->sizL = WMCreateLabel(panel->lowerF);
WMSetWidgetBackgroundColor(panel->sizL, dark);
WMSetLabelText(panel->sizL, _("Size"));
WMSetLabelFont(panel->sizL, font);
WMSetLabelTextColor(panel->sizL, white);
WMSetLabelRelief(panel->sizL, WRSunken);
WMSetLabelTextAlignment(panel->sizL, WACenter);
panel->sizT = WMCreateTextField(panel->lowerF);
/* WMSetTextFieldAlignment(panel->sizT, WARight); */
panel->sizLs = WMCreateList(panel->lowerF);
WMSetListAction(panel->sizLs, sizeClick, panel);
WMReleaseFont(font);
WMReleaseColor(white);
WMReleaseColor(dark);
panel->setB = WMCreateCommandButton(panel->win);
WMResizeWidget(panel->setB, 70, 24);
WMMoveWidget(panel->setB, 240, DEF_HEIGHT - (BUTTON_SPACE_HEIGHT - 5));
WMSetButtonText(panel->setB, _("Set"));
WMSetButtonAction(panel->setB, setClickedAction, panel);
panel->revertB = WMCreateCommandButton(panel->win);
WMResizeWidget(panel->revertB, 70, 24);
WMMoveWidget(panel->revertB, 80, DEF_HEIGHT - (BUTTON_SPACE_HEIGHT - 5));
WMSetButtonText(panel->revertB, _("Revert"));
WMSetButtonAction(panel->revertB, revertClickedAction, panel);
WMRealizeWidget(panel->win);
WMMapSubwidgets(panel->upperF);
WMMapSubwidgets(panel->lowerF);
WMMapSubwidgets(panel->split);
WMMapSubwidgets(panel->win);
WMUnmapWidget(panel->revertB);
arrangeLowerFrame(panel);
scr->sharedFontPanel = panel;
/* register notification observers */
WMAddNotificationObserver(notificationObserver, panel,
WMViewSizeDidChangeNotification, WMWidgetView(panel->win));
WMAddNotificationObserver(notificationObserver, panel,
WMViewSizeDidChangeNotification, WMWidgetView(panel->upperF));
WMAddNotificationObserver(notificationObserver, panel,
WMViewSizeDidChangeNotification, WMWidgetView(panel->lowerF));
listFamilies(scr, panel);
return panel;
}
void WMFreeFontPanel(WMFontPanel * panel)
{
if (panel == WMWidgetScreen(panel->win)->sharedFontPanel) {
WMWidgetScreen(panel->win)->sharedFontPanel = NULL;
}
WMRemoveNotificationObserver(panel);
WMUnmapWidget(panel->win);
WMDestroyWidget(panel->win);
wfree(panel);
}
void WMShowFontPanel(WMFontPanel * panel)
{
WMMapWidget(panel->win);
}
void WMHideFontPanel(WMFontPanel * panel)
{
WMUnmapWidget(panel->win);
}
WMFont *WMGetFontPanelFont(WMFontPanel * panel)
{
return WMGetTextFieldFont(panel->sampleT);
}
void WMSetFontPanelFont(WMFontPanel * panel, const char *fontName)
{
int fname_len;
FcPattern *pattern;
FcChar8 *family, *style;
double size;
if (!isXLFD(fontName, &fname_len)) {
/* maybe its proper fontconfig and we can parse it */
pattern = FcNameParse((const FcChar8 *) fontName);
} else {
/* maybe its proper xlfd and we can convert it to an FcPattern */
pattern = XftXlfdParse(fontName, False, False);
/*//FcPatternPrint(pattern); */
}
if (!pattern)
return;
if (FcPatternGetString(pattern, FC_FAMILY, 0, &family) == FcResultMatch)
if (FcPatternGetString(pattern, FC_STYLE, 0, &style) == FcResultMatch)
if (FcPatternGetDouble(pattern, "pixelsize", 0, &size) == FcResultMatch)
setFontPanelFontName(panel, (char *)family, (char *)style, size);
FcPatternDestroy(pattern);
}
void WMSetFontPanelAction(WMFontPanel * panel, WMAction2 * action, void *data)
{
panel->action = action;
panel->data = data;
}
static void arrangeLowerFrame(FontPanel * panel)
{
int width = WMWidgetWidth(panel->lowerF) - 55 - 30;
int height = WMWidgetHeight(panel->split) - WMWidgetHeight(panel->upperF);
int fw, tw, sw;
#define LABEL_HEIGHT 20
height -= WMGetSplitViewDividerThickness(panel->split);
height -= LABEL_HEIGHT + 8;
fw = (125 * width) / 235;
tw = (110 * width) / 235;
sw = 55;
WMMoveWidget(panel->famL, 10, 0);
WMResizeWidget(panel->famL, fw, LABEL_HEIGHT);
WMMoveWidget(panel->famLs, 10, 23);
WMResizeWidget(panel->famLs, fw, height);
WMMoveWidget(panel->typL, 10 + fw + 3, 0);
WMResizeWidget(panel->typL, tw, LABEL_HEIGHT);
WMMoveWidget(panel->typLs, 10 + fw + 3, 23);
WMResizeWidget(panel->typLs, tw, height);
WMMoveWidget(panel->sizL, 10 + fw + 3 + tw + 3, 0);
WMResizeWidget(panel->sizL, sw + 4, LABEL_HEIGHT);
WMMoveWidget(panel->sizT, 10 + fw + 3 + tw + 3, 23);
WMResizeWidget(panel->sizT, sw + 4, 20);
WMMoveWidget(panel->sizLs, 10 + fw + 3 + tw + 3, 46);
WMResizeWidget(panel->sizLs, sw + 4, height - 23);
}
#define NUM_FIELDS 14
static int isXLFD(const char *font, int *length_ret)
{
int c = 0;
*length_ret = 0;
while (*font) {
(*length_ret)++;
if (*font++ == '-')
c++;
}
return c == NUM_FIELDS;
}
typedef struct {
char *typeface;
WMArray *sizes;
} Typeface;
typedef struct {
char *name; /* gotta love simplicity */
WMArray *typefaces;
} Family;
static int compare_int(const void *a, const void *b)
{
int i1 = *(int *)a;
int i2 = *(int *)b;
if (i1 < i2)
return -1;
else if (i1 > i2)
return 1;
else
return 0;
}
static void addSizeToTypeface(Typeface * face, int size)
{
if (size == 0) {
int j;
for (j = 0; j < wlengthof(scalableFontSizes); j++) {
size = scalableFontSizes[j];
if (!WMCountInArray(face->sizes, (void *)(uintptr_t) size)) {
WMAddToArray(face->sizes, (void *)(uintptr_t) size);
}
}
WMSortArray(face->sizes, compare_int);
} else {
if (!WMCountInArray(face->sizes, (void *)(uintptr_t) size)) {
WMAddToArray(face->sizes, (void *)(uintptr_t) size);
WMSortArray(face->sizes, compare_int);
}
}
}
static void addTypefaceToXftFamily(Family * fam, const char *style)
{
Typeface *face;
WMArrayIterator i;
if (fam->typefaces) {
WM_ITERATE_ARRAY(fam->typefaces, face, i) {
if (strcmp(face->typeface, style) != 0)
continue; /* go to next interation */
addSizeToTypeface(face, 0);
return;
}
} else {
fam->typefaces = WMCreateArray(4);
}
face = wmalloc(sizeof(Typeface));
face->typeface = wstrdup(style);
face->sizes = WMCreateArray(4);
addSizeToTypeface(face, 0);
WMAddToArray(fam->typefaces, face);
}
/*
* families (same family name) (Hashtable of family -> array)
* registries (same family but different registries)
*
*/
static void addFontToXftFamily(WMHashTable * families, const char *name, const char *style)
{
WMArrayIterator i;
WMArray *array;
Family *fam;
array = WMHashGet(families, name);
if (array) {
WM_ITERATE_ARRAY(array, fam, i) {
if (strcmp(fam->name, name) == 0)
addTypefaceToXftFamily(fam, style);
return;
}
}
array = WMCreateArray(8);
fam = wmalloc(sizeof(Family));
fam->name = wstrdup(name);
addTypefaceToXftFamily(fam, style);
WMAddToArray(array, fam);
WMHashInsert(families, fam->name, array);
}
static void listFamilies(WMScreen * scr, WMFontPanel * panel)
{
FcObjectSet *os = 0;
FcFontSet *fs;
FcPattern *pat;
WMHashTable *families;
WMHashEnumerator enumer;
WMArray *array;
int i;
pat = FcPatternCreate();
os = FcObjectSetBuild(FC_FAMILY, FC_STYLE, NULL);
fs = FcFontList(0, pat, os);
if (!fs) {
WMRunAlertPanel(scr, panel->win, _("Error"),
_("Could not init font config library\n"), _("OK"), NULL, NULL);
return;
}
if (pat)
FcPatternDestroy(pat);
families = WMCreateStringHashTable();
if (fs) {
for (i = 0; i < fs->nfont; i++) {
FcChar8 *family;
FcChar8 *style;
if (FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, &family) == FcResultMatch)
if (FcPatternGetString(fs->fonts[i], FC_STYLE, 0, &style) == FcResultMatch)
addFontToXftFamily(families, (char *)family, (char *)style);
}
FcFontSetDestroy(fs);
}
enumer = WMEnumerateHashTable(families);
while ((array = WMNextHashEnumeratorItem(&enumer))) {
WMArrayIterator i;
Family *fam;
char buffer[256];
WMListItem *item;
WM_ITERATE_ARRAY(array, fam, i) {
strlcpy(buffer, fam->name, sizeof(buffer));
item = WMAddListItem(panel->famLs, buffer);
item->clientData = fam;
}
WMFreeArray(array);
}
WMSortListItems(panel->famLs);
WMFreeHashTable(families);
}
static void getSelectedFont(FontPanel * panel, char buffer[], int bufsize)
{
WMListItem *item;
Family *family;
Typeface *face;
char *size;
item = WMGetListSelectedItem(panel->famLs);
if (!item)
return;
family = (Family *) item->clientData;
item = WMGetListSelectedItem(panel->typLs);
if (!item)
return;
face = (Typeface *) item->clientData;
size = WMGetTextFieldText(panel->sizT);
snprintf(buffer, bufsize, "%s:style=%s:pixelsize=%s", family->name, face->typeface, size);
wfree(size);
}
static void preview(FontPanel * panel)
{
char buffer[512];
WMFont *font;
getSelectedFont(panel, buffer, sizeof(buffer));
font = WMCreateFont(WMWidgetScreen(panel->win), buffer);
if (font) {
WMSetTextFieldFont(panel->sampleT, font);
WMReleaseFont(font);
}
}
static void familyClick(WMWidget * w, void *data)
{
WMList *lPtr = (WMList *) w;
WMListItem *item;
Family *family;
Typeface *face;
FontPanel *panel = (FontPanel *) data;
WMArrayIterator i;
/* current typeface and size */
char *oface = NULL;
char *osize = NULL;
int facei = -1;
int sizei = -1;
/* must try to keep the same typeface and size for the new family */
item = WMGetListSelectedItem(panel->typLs);
if (item)
oface = wstrdup(item->text);
osize = WMGetTextFieldText(panel->sizT);
item = WMGetListSelectedItem(lPtr);
family = (Family *) item->clientData;
WMClearList(panel->typLs);
WM_ITERATE_ARRAY(family->typefaces, face, i) {
char buffer[256];
int top = 0;
WMListItem *fitem;
strlcpy(buffer, face->typeface, sizeof(buffer));
if (strcasecmp(face->typeface, "Roman") == 0)
top = 1;
if (strcasecmp(face->typeface, "Regular") == 0)
top = 1;
if (top)
fitem = WMInsertListItem(panel->typLs, 0, buffer);
else
fitem = WMAddListItem(panel->typLs, buffer);
fitem->clientData = face;
}
if (oface) {
facei = WMFindRowOfListItemWithTitle(panel->typLs, oface);
wfree(oface);
}
if (facei < 0) {
facei = 0;
}
WMSelectListItem(panel->typLs, facei);
typefaceClick(panel->typLs, panel);
if (osize) {
sizei = WMFindRowOfListItemWithTitle(panel->sizLs, osize);
}
if (sizei >= 0) {
WMSelectListItem(panel->sizLs, sizei);
sizeClick(panel->sizLs, panel);
}
if (osize)
wfree(osize);
preview(panel);
}
static void typefaceClick(WMWidget * w, void *data)
{
FontPanel *panel = (FontPanel *) data;
WMListItem *item;
Typeface *face;
WMArrayIterator i;
char buffer[32];
char *osize = NULL;
int sizei = -1;
void *size;
/* Parameter not used, but tell the compiler that it is ok */
(void) w;
osize = WMGetTextFieldText(panel->sizT);
item = WMGetListSelectedItem(panel->typLs);
face = (Typeface *) item->clientData;
WMClearList(panel->sizLs);
WM_ITERATE_ARRAY(face->sizes, size, i) {
if (size != NULL) {
int size_int = (intptr_t) size;
sprintf(buffer, "%i", size_int);
WMAddListItem(panel->sizLs, buffer);
}
}
if (osize) {
sizei = WMFindRowOfListItemWithTitle(panel->sizLs, osize);
}
if (sizei < 0) {
sizei = WMFindRowOfListItemWithTitle(panel->sizLs, "12");
}
if (sizei < 0) {
sizei = 0;
}
WMSelectListItem(panel->sizLs, sizei);
WMSetListPosition(panel->sizLs, sizei);
sizeClick(panel->sizLs, panel);
if (osize)
wfree(osize);
preview(panel);
}
static void sizeClick(WMWidget * w, void *data)
{
FontPanel *panel = (FontPanel *) data;
WMListItem *item;
/* Parameter not used, but tell the compiler that it is ok */
(void) w;
item = WMGetListSelectedItem(panel->sizLs);
WMSetTextFieldText(panel->sizT, item->text);
WMSelectTextFieldRange(panel->sizT, wmkrange(0, strlen(item->text)));
preview(panel);
}
static void setFontPanelFontName(FontPanel * panel, const char *family, const char *style, double size)
{
int famrow;
int stlrow;
int sz;
char asize[64];
void *vsize;
WMListItem *item;
Family *fam;
Typeface *face;
WMArrayIterator i;
famrow = WMFindRowOfListItemWithTitle(panel->famLs, family);
if (famrow < 0) {
famrow = 0;
return;
}
WMSelectListItem(panel->famLs, famrow);
WMSetListPosition(panel->famLs, famrow);
WMClearList(panel->typLs);
item = WMGetListSelectedItem(panel->famLs);
fam = (Family *) item->clientData;
WM_ITERATE_ARRAY(fam->typefaces, face, i) {
char buffer[256];
int top = 0;
WMListItem *fitem;
strlcpy(buffer, face->typeface, sizeof(buffer));
if (strcasecmp(face->typeface, "Roman") == 0)
top = 1;
if (top)
fitem = WMInsertListItem(panel->typLs, 0, buffer);
else
fitem = WMAddListItem(panel->typLs, buffer);
fitem->clientData = face;
}
stlrow = WMFindRowOfListItemWithTitle(panel->typLs, style);
if (stlrow < 0) {
stlrow = 0;
return;
}
WMSelectListItem(panel->typLs, stlrow);
item = WMGetListSelectedItem(panel->typLs);
face = (Typeface *) item->clientData;
WMClearList(panel->sizLs);
WM_ITERATE_ARRAY(face->sizes, vsize, i) {
char buffer[32];
if (vsize != NULL) {
int size_int = (intptr_t) vsize;
sprintf(buffer, "%i", size_int);
WMAddListItem(panel->sizLs, buffer);
}
}
snprintf(asize, sizeof(asize) - 1, "%d", (int)(size + 0.5));
sz = WMFindRowOfListItemWithTitle(panel->sizLs, asize);
if (sz < 0) {
return;
}
WMSelectListItem(panel->sizLs, sz);
sizeClick(panel->sizLs, panel);
return;
}
+7 -18
View File
@@ -629,7 +629,8 @@ WMScreen *WMCreateScreenWithRContext(Display * display, int screen, RContext * c
scrPtr->rootWin = RootWindow(display, screen);
scrPtr->fontCache = WMCreateStringHashTable();
// Will be initialized lazily in wings-rs/src/screen.rs.
scrPtr->fontCache = NULL;
scrPtr->xftdraw = XftDrawCreate(scrPtr->display, W_DRAWABLE(scrPtr), scrPtr->visual, scrPtr->colormap);
@@ -724,14 +725,14 @@ WMScreen *WMCreateScreenWithRContext(Display * display, int screen, RContext * c
gcv.graphics_exposures = False;
gcv.function = GXxor;
gcv.foreground = W_PIXEL(scrPtr->white);
gcv.foreground = WMColorPixel(scrPtr->white);
if (gcv.foreground == 0)
gcv.foreground = 1;
scrPtr->xorGC = XCreateGC(display, W_DRAWABLE(scrPtr), GCFunction
| GCGraphicsExposures | GCForeground, &gcv);
gcv.function = GXxor;
gcv.foreground = W_PIXEL(scrPtr->gray);
gcv.foreground = WMColorPixel(scrPtr->gray);
gcv.subwindow_mode = IncludeInferiors;
scrPtr->ixorGC = XCreateGC(display, W_DRAWABLE(scrPtr), GCFunction
| GCGraphicsExposures | GCForeground | GCSubwindowMode, &gcv);
@@ -742,8 +743,8 @@ WMScreen *WMCreateScreenWithRContext(Display * display, int screen, RContext * c
scrPtr->clipGC = XCreateGC(display, W_DRAWABLE(scrPtr), GCFunction | GCGraphicsExposures, &gcv);
stipple = XCreateBitmapFromData(display, W_DRAWABLE(scrPtr), STIPPLE_BITS, STIPPLE_WIDTH, STIPPLE_HEIGHT);
gcv.foreground = W_PIXEL(scrPtr->darkGray);
gcv.background = W_PIXEL(scrPtr->gray);
gcv.foreground = WMColorPixel(scrPtr->darkGray);
gcv.background = WMColorPixel(scrPtr->gray);
gcv.fill_style = FillStippled;
gcv.stipple = stipple;
scrPtr->stippleGC = XCreateGC(display, W_DRAWABLE(scrPtr),
@@ -909,18 +910,6 @@ WMScreen *WMCreateScreenWithRContext(Display * display, int screen, RContext * c
return scrPtr;
}
void WMSetWidgetDefaultFont(WMScreen * scr, WMFont * font)
{
WMReleaseFont(scr->normalFont);
scr->normalFont = WMRetainFont(font);
}
void WMSetWidgetDefaultBoldFont(WMScreen * scr, WMFont * font)
{
WMReleaseFont(scr->boldFont);
scr->boldFont = WMRetainFont(font);
}
void WMHangData(WMWidget * widget, void *data)
{
W_VIEW(widget)->hangedData = data;
@@ -1011,7 +1000,7 @@ void WMSetWidgetBackgroundColor(WMWidget * w, WMColor * color)
WMRedisplayWidget(w);
}
WMColor *WMGetWidgetBackgroundColor(WMWidget * w)
WMColor * WMGetWidgetBackgroundColor(WMWidget * w)
{
return W_VIEW(w)->backColor;
}
+19
View File
@@ -0,0 +1,19 @@
[package]
name = "wings-rs-tests"
version = "0.1.0"
edition = "2024"
[dependencies]
libc = "0.2"
insta = "1.47"
insta-image = { version = "1.0", features = ["png"] }
png = "0.18"
tempdir = "0.3.7"
wings-rs = { path = "../wings-rs" }
wrlib-rs = { path = "../../wrlib-rs" }
wutil-rs = { path = "../../wutil-rs" }
x11 = "2.21.0"
[profile.dev.package]
insta.opt-level = 3
similar.opt-level = 3
+100
View File
@@ -0,0 +1,100 @@
AUTOMAKE_OPTIONS =
RUST_SOURCES = \
examples/font_panel.rs \
src/headless/mod.rs \
src/headless/snapshots/wings_rs_tests__headless__xvfb__tests__blank_screen.snap \
src/headless/snapshots/wings_rs_tests__headless__xvfb__tests__blank_screen.snap.png \
src/headless/snapshots/wings_rs_tests__headless__xvfb__tests__snowlamp_in_window.snap \
src/headless/snapshots/wings_rs_tests__headless__xvfb__tests__snowlamp_in_window.snap.png \
src/headless/snapshots/wings_rs_tests__headless__xvfb__tests__xeyes.snap \
src/headless/snapshots/wings_rs_tests__headless__xvfb__tests__xeyes.snap.png \
src/headless/snapshots/wings_rs_tests__headless__xwd__tests__snowlamp_encoded.snap \
src/headless/snapshots/wings_rs_tests__headless__xwd__tests__snowlamp_encoded.snap.png \
src/headless/snowlamp.xwd \
src/headless/xvfb.rs \
src/headless/xwd.rs \
src/lib.rs \
src/logo_data.rs \
tests/font_panel_tests.rs \
tests/system_pixmap_tests.rs \
tests/wmpixmap_tests.rs \
tests/snapshots/font_panel_tests__font_panel.snap \
tests/snapshots/font_panel_tests__font_panel.snap.png \
tests/snapshots/system_pixmap_tests__arrow_down.snap \
tests/snapshots/system_pixmap_tests__arrow_down.snap.png \
tests/snapshots/system_pixmap_tests__arrow_left.snap \
tests/snapshots/system_pixmap_tests__arrow_left.snap.png \
tests/snapshots/system_pixmap_tests__arrow_right.snap \
tests/snapshots/system_pixmap_tests__arrow_right.snap.png \
tests/snapshots/system_pixmap_tests__arrow_up.snap \
tests/snapshots/system_pixmap_tests__arrow_up.snap.png \
tests/snapshots/system_pixmap_tests__check_mark.snap \
tests/snapshots/system_pixmap_tests__check_mark.snap.png \
tests/snapshots/system_pixmap_tests__highlighted_arrow_down.snap \
tests/snapshots/system_pixmap_tests__highlighted_arrow_down.snap.png \
tests/snapshots/system_pixmap_tests__highlighted_arrow_left.snap \
tests/snapshots/system_pixmap_tests__highlighted_arrow_left.snap.png \
tests/snapshots/system_pixmap_tests__highlighted_arrow_right.snap \
tests/snapshots/system_pixmap_tests__highlighted_arrow_right.snap.png \
tests/snapshots/system_pixmap_tests__highlighted_arrow_up.snap \
tests/snapshots/system_pixmap_tests__highlighted_arrow_up.snap.png \
tests/snapshots/system_pixmap_tests__highlighted_return_arrow.snap \
tests/snapshots/system_pixmap_tests__highlighted_return_arrow.snap.png \
tests/snapshots/system_pixmap_tests__return_arrow.snap \
tests/snapshots/system_pixmap_tests__return_arrow.snap.png \
tests/snapshots/system_pixmap_tests__scroller_dimple.snap \
tests/snapshots/system_pixmap_tests__scroller_dimple.snap.png \
tests/snapshots/wmpixmap_tests__empty_pixmap_on_window.snap \
tests/snapshots/wmpixmap_tests__empty_pixmap_on_window.snap.png \
tests/snapshots/wmpixmap_tests__empty_window.snap \
tests/snapshots/wmpixmap_tests__empty_window.snap.png \
tests/snapshots/wmpixmap_tests__from_file_blended_base_pixmap.snap \
tests/snapshots/wmpixmap_tests__from_file_blended_base_pixmap.snap.png \
tests/snapshots/wmpixmap_tests__from_file_blended_cleared_window.snap \
tests/snapshots/wmpixmap_tests__from_file_blended_cleared_window.snap.png \
tests/snapshots/wmpixmap_tests__from_file_blended_with_grey.snap \
tests/snapshots/wmpixmap_tests__from_file_blended_with_grey.snap.png \
tests/snapshots/wmpixmap_tests__from_file_pixmap.snap \
tests/snapshots/wmpixmap_tests__from_file_pixmap.snap.png \
tests/snapshots/wmpixmap_tests__from_file_scaled_blended_base_pixmap.snap \
tests/snapshots/wmpixmap_tests__from_file_scaled_blended_base_pixmap.snap.png \
tests/snapshots/wmpixmap_tests__from_file_scaled_blended_cleared_window.snap \
tests/snapshots/wmpixmap_tests__from_file_scaled_blended_cleared_window.snap.png \
tests/snapshots/wmpixmap_tests__from_file_scaled_blended_non_integral_base_pixmap.snap \
tests/snapshots/wmpixmap_tests__from_file_scaled_blended_non_integral_base_pixmap.snap.png \
tests/snapshots/wmpixmap_tests__from_file_scaled_blended_non_integral_cleared_window.snap \
tests/snapshots/wmpixmap_tests__from_file_scaled_blended_non_integral_cleared_window.snap.png \
tests/snapshots/wmpixmap_tests__from_file_scaled_blended_non_integral_scaled.snap \
tests/snapshots/wmpixmap_tests__from_file_scaled_blended_non_integral_scaled.snap.png \
tests/snapshots/wmpixmap_tests__from_file_scaled_blended_with_grey.snap \
tests/snapshots/wmpixmap_tests__from_file_scaled_blended_with_grey.snap.png \
tests/snapshots/wmpixmap_tests__from_r_image_no_mask.snap \
tests/snapshots/wmpixmap_tests__from_r_image_no_mask.snap.png \
tests/snapshots/wmpixmap_tests__from_r_image_with_mask.snap \
tests/snapshots/wmpixmap_tests__from_r_image_with_mask.snap.png \
tests/snapshots/wmpixmap_tests__pixmap_from_xpm_data.snap \
tests/snapshots/wmpixmap_tests__pixmap_from_xpm_data.snap.png
RUST_EXTRA = \
Cargo.lock \
Cargo.toml
Cargo.lock:
cargo build
rustlib: $(RUST_SOURCES) $(RUST_EXTRA)
cargo build
check-local: rustlib
clean-local:
$(CARGO) clean
all: rustlib
# Tests run with cargo-nextest because it puts each application test in its own
# process, and WINGs currently assumes that it is running in a single-threaded
# environment.
test: rustlib
LD_LIBRARY_PATH=../.libs:../../wrlib/.libs $(CARGO) nextest run
+13
View File
@@ -0,0 +1,13 @@
fn main() {
println!("cargo::rustc-link-search=../.libs");
println!("cargo::rustc-link-search=../../wrlib/.libs");
println!("cargo::rustc-link-arg=-lX11");
println!("cargo::rustc-link-arg=-lWUtil");
println!("cargo::rustc-link-arg=-lWINGs");
println!("cargo::rustc-link-arg=-lwraster");
println!("cargo::rustc-link-arg=-lX11");
println!("cargo::rustc-link-arg=-lXft");
println!("cargo::rustc-link-arg=-lpango-1.0");
println!("cargo::rustc-link-arg=-lpangoxft-1.0");
println!("cargo::rustc-link-arg=-lpangoft2-1.0");
}
@@ -0,0 +1,11 @@
use wings_rs::{font_panel::FontPanel, WINGsP::WMScreenMainLoop};
use wings_rs_tests::LiveApplication;
fn main() {
let app = LiveApplication::new("WMFontPanel");
let mut font_panel = FontPanel::new(app.screen.as_ptr()).expect("could not create font panel");
font_panel.show();
unsafe {
WMScreenMainLoop(app.screen.as_ptr());
}
}
+133
View File
@@ -0,0 +1,133 @@
//! Provides utilites for integration tests that render to a headless X11 server.
//!
//! ## X11 `DISPLAY` value allocation
//!
//! When starting headless X servers, some care needs to be taken in selecting
//! display numbers. An X server needs to have a unique display number and will
//! not start if it is assigned a display number that is already in use.
//!
//! Local X servers usually leave a lockfile in `/tmp`, like `/tmp/.X0-lock` for
//! display 0. Avoiding the use of display numbers that have a corresponding
//! lockfile will get you part of the way towards avoiding collisions, but
//! further coordination is provided by the [`Lock`] and
//! [`DisplayNumberRegistry`] structs. These interfaces provide a mechanism for
//! claiming a display number in a way that coordinates across processes and
//! should be more efficient than simply scanning `/tmp` each time a new display
//! number is needed.
use std::{
fs,
io,
path::PathBuf,
sync::atomic::{self, AtomicU16},
};
pub mod xvfb;
pub mod xwd;
/// Represents a lock on a display number, which is released on drop.
pub struct Lock {
display: u16,
path: PathBuf,
}
/// Errors that may occur when trying to lock a display number.
pub enum LockError {
Io(io::Error),
Locked,
}
impl Lock {
pub(crate) fn new(display: u16, path: PathBuf) -> Self {
Lock {
display,
path
}
}
/// Returns the locked `DISPLAY` value.
pub fn display(&self) -> u16 {
self.display
}
}
impl Drop for Lock {
fn drop(&mut self) {
// `file` should be unlinked already, but we explicitly try to delete it
// and unwrap the result so that errors aren't dropped silently.
match fs::remove_file(&self.path) {
Ok(_) => (),
Err(e) if e.kind() == io::ErrorKind::NotFound => (),
Err(e) => panic!("could not unlink lock file: {:?}", e),
}
}
}
// Shared across threads in this process to help keep us from repeatedly asking
// for the same DISPLAY value.
static NEXT_DISPLAY: AtomicU16 = AtomicU16::new(32);
/// Coordinates on the value of `DISPLAY` to use when creating new X11 servers.
///
/// Methods on `DisplayNumberRegistry` may be called across threads or in
/// different processes to ensure that X servers are created with unique
/// display numbers.
pub struct DisplayNumberRegistry;
impl DisplayNumberRegistry {
/// Returns a lock on the first local display number (the `N` in `:N` for
/// the X client `DISPLAY` environment variable) that is not currently in
/// use.
///
/// If no display numbers are available, returns `None`.
///
/// To avoid collisions with other processes, attempts are made to sniff out
/// which display numbers are already in use by looking for lock files
/// matching the pattern `/tmp/.X*-lock`. If such a file exists, its
/// display number will not be used.
///
/// When an available display number is found, an empty lockfile is
/// atomically created (and marking the display number as claimed to other
/// well-behaved processes). When `Xvfb` is run using that display number,
/// it silently overwrites the empty lockfile.
///
/// Any extant lockfile should be deleted by the `Drop` impl for [`Lock`],
/// although tests that panic may leave stale lockfiles behind.
pub fn next_unused_display() -> io::Result<Lock> {
loop {
let prev = NEXT_DISPLAY.fetch_add(1, atomic::Ordering::SeqCst);
if prev == u16::MAX {
return Err(io::Error::other("display numbers exhausted; check /tmp/.X{n}-lock"));
}
let next = prev + 1;
let path = PathBuf::from(format!("/tmp/.X{}-lock", next));
match fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&path) {
Ok(_) => return Ok(Lock::new(next, path)),
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => continue,
Err(e) => return Err(e),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn find_unused_display() {
assert!(DisplayNumberRegistry::next_unused_display().is_ok());
assert_ne!(
DisplayNumberRegistry::next_unused_display()
.unwrap()
.display,
DisplayNumberRegistry::next_unused_display()
.unwrap()
.display
);
}
}
@@ -0,0 +1,7 @@
---
source: src/headless/xvfb.rs
assertion_line: 387
expression: xwd.into_png().unwrap()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,7 @@
---
source: src/headless/xvfb.rs
assertion_line: 416
expression: compressed
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

@@ -0,0 +1,7 @@
---
source: src/headless/xvfb.rs
assertion_line: 402
expression: compressed
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

@@ -0,0 +1,7 @@
---
source: src/headless/xwd.rs
assertion_line: 321
expression: snowlamp_xwd.into_png().unwrap()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.
+471
View File
@@ -0,0 +1,471 @@
//! Provides utilities for headlessly running and taking screenshots of X11
//! programs in unit and integration tests.
//!
//! This uses [Xvfb](https://x.org/releases/X11R7.7/doc/man/man1/Xvfb.1.xhtml).
//!
//! Tests of this module invoke `xeyes` and `xwud` subprocesses. For all tests
//! to pass, you may need to install these X11 utilities and make them available
//! on your `PATH`.
use std::{
ffi::CString,
fs, io,
process::{Child, Command, ExitStatus, Stdio},
thread,
time::{Duration, Instant},
};
use tempdir::TempDir;
use super::xwd;
use super::Lock;
/// Arguments for `Xvfb`. Use `XvfbArgs::default()` for default values.
#[derive(Clone, Copy, Debug)]
pub struct XvfbArgs {
/// Width of the X11 screen.
pub width: u32,
/// Height of the X11 screen.
pub height: u32,
/// Bit depth of the X11 screen.
///
/// This should probably be one of 8, 16, or 24. (And if you don't choose 24
/// bits, you should have a good reason why.)
pub depth: u8,
}
impl Default for XvfbArgs {
fn default() -> Self {
XvfbArgs {
width: 640,
height: 480,
depth: 24,
}
}
}
/// A captive `Xvfb` process, with affordances for running subprocesses that
/// connect to it and taking screenshots.
///
/// When dropped, child processes will be killed automatically.
pub struct XvfbProcess {
lock: Lock,
framebuffer_directory: TempDir,
process: Child,
subprocesses: Vec<(Child, SubprocessMonitor)>,
is_shutdown: bool,
}
impl XvfbProcess {
/// Attempts to start `Xvfb` with default options.
///
/// Returns `None` if an error occurs while starting the `Xvfb` process.
pub fn start_default(lock: Lock) -> SubprocessResult<Self> {
XvfbProcess::start(lock, XvfbArgs::default())
}
/// Starts an `Xvfb` process with options specified by `args`.
///
/// Returns `None` if an error occurs while starting the `Xvfb` process.
///
/// This function connects to the `Xvfb` server briefly to try to ensure
/// that Xvfb is ready for clients to connect to it.
pub fn start(lock: Lock, args: XvfbArgs) -> SubprocessResult<Self> {
let framebuffer_directory = TempDir::new("wings_rs_xvfb").map_err(|e| {
SubprocessError::new("Xvfb temp dir".into(), SubprocessErrorType::Spawn(e))
})?;
#[cfg(target_os = "linux")]
unsafe {
// Kill children if this parent process dies catastrophically (e.g.,
// a test raises SIGABRT).
libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL);
}
let process = Command::new("Xvfb")
.arg(format!(":{}", lock.display))
.arg("-screen")
.arg("0")
.arg(format!("{}x{}x{}", args.width, args.height, args.depth))
.arg("-fbdir")
.arg(format!("{}", framebuffer_directory.path().display()))
.stderr(Stdio::piped())
.spawn()
.map_err(|e| SubprocessError::new("Xvfb".into(), SubprocessErrorType::Spawn(e)))?;
let process = XvfbProcess {
lock,
framebuffer_directory,
process,
subprocesses: Vec::new(),
is_shutdown: false,
};
let now = Instant::now();
let display_name = CString::new(
format!(":{}", process.lock.display)
).expect("could not construct display name");
while now.elapsed() < Duration::from_secs(1) {
unsafe {
let display = x11::xlib::XOpenDisplay(display_name.as_ptr());
if !display.is_null() {
x11::xlib::XCloseDisplay(display);
break;
}
}
thread::sleep(Duration::from_millis(10));
}
return Ok(process);
}
/// Returns the number in the `DISPLAY` environment variable to connect to
/// this X11 server.
pub fn display(&self) -> u16 {
self.lock.display
}
/// Provides access to the `Xvfb` child process wrapped by `self`.
pub fn process(&mut self) -> &mut Child {
&mut self.process
}
/// Attempts to clean up all child processes. Returns any errors encountered, or `Ok` if none.
///
/// Child processes are cleaned up with the following escalating steps:
///
/// * Call [`SubprocessMonitor::on_xvfb_shutdown`] on each child's monitor.
/// * Kill each of them directly (with [`Child::kill`]).
/// * Kill the `Xvfb` process (which may cause any children still connected to that display server to exit).
pub fn shutdown(&mut self) -> Result<(), Vec<SubprocessError>> {
if self.is_shutdown {
return Ok(());
}
self.is_shutdown = true;
let mut errors = Vec::new();
for (child, monitor) in self.subprocesses.iter_mut() {
if let Err(e) = monitor.on_xvfb_shutdown(child) {
errors.push(e);
}
match child.try_wait() {
Ok(Some(status)) => {
if let Err(e) = monitor.check_subprocess_exit(status) {
errors.push(e);
}
}
Ok(None) => {
if let Err(e) = child.kill() {
errors.push(SubprocessError::new(
monitor.name.clone(),
SubprocessErrorType::Kill(e),
));
}
}
Err(e) => errors.push(SubprocessError::new(
monitor.name.clone(),
SubprocessErrorType::Status(e),
)),
}
}
if let Err(e) = self.process.kill() {
errors.push(SubprocessError::new(
"Xvfb".into(),
SubprocessErrorType::Kill(e),
));
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
/// Runs a simple command in this display server. No command line parsing is
/// done, so `run("ls -la")` will probably not do what you expect (because
/// there is no binary on your `PATH` called `ls -la`). Use [`XvfbProcess::run_args`]
/// to provide arguments to `command`.
pub fn run(&mut self, command: impl Into<String>) -> Result<(), SubprocessError> {
let name = command.into();
self.run_args(name.clone(), &name, |_c| {})
}
/// Runs a command that you can provide arguments to in this display
/// server.
///
/// The `f` parameter can be used to modify the `Command` before it is
/// run (e.g., by adding arguments). For example:
///
/// ```
/// # use wings_rs_tests::headless::DisplayNumberRegistry;
/// # use wings_rs_tests::headless::xvfb::XvfbProcess;
/// # fn main() {
/// # let mut xvfb = XvfbProcess::start_default(DisplayNumberRegistry::next_unused_display().unwrap()).unwrap();
/// xvfb.run_args(
/// "demo of xeyes, angel-style",
/// "/usr/bin/xeyes",
/// |cmd| { cmd.arg("-biblicallyAccurate"); },
/// ).unwrap();
/// # }
/// ```
pub fn run_args(
&mut self,
name: impl Into<String>,
command: impl AsRef<std::ffi::OsStr>,
f: impl FnOnce(&mut Command),
) -> Result<(), SubprocessError> {
let mut command = Command::new(command);
command.env("DISPLAY", format!(":{}", self.lock.display));
f(&mut command);
let mut monitor = SubprocessMonitor::new(name.into(), command);
monitor.with_subprocess_exit(SubprocessMonitor::require_clean_exit());
let child = monitor.start()?;
self.subprocesses.push((child, monitor));
Ok(())
}
/// Checks all subprocesses started with the [`XvfbProcess::run`] family of
/// methods and returns errors for any that have an error status, or `Ok` if
/// none do.
pub fn check(&mut self) -> Result<(), Vec<SubprocessError>> {
let errors: Vec<_> = self
.subprocesses
.iter_mut()
.filter_map(|(child, monitor)| match child.try_wait() {
Ok(Some(status)) => monitor.check_subprocess_exit(status).err(),
_ => None,
})
.collect();
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
/// Returns a screenshot of the current framebuffer contents in `xwd(1)`
/// format.
///
/// For the same in a more generally useful format, try [`XvfbProcess::png_screenshot`].
pub fn xwd_screenshot(&self) -> io::Result<xwd::XwdImage> {
let path = self.framebuffer_directory.path().join("Xvfb_screen0");
Ok(xwd::XwdImage::read(&mut fs::read(path)?.into_iter()))
}
/// Returns a screenshot of the current framebuffer contents in PNG format.
///
/// Panics on errors.
pub fn png_screenshot(&self) -> Vec<u8> {
self.xwd_screenshot().unwrap().into_png().unwrap()
}
}
impl Drop for XvfbProcess {
fn drop(&mut self) {
self.shutdown().unwrap();
}
}
pub type SubprocessResult<T> = Result<T, SubprocessError>;
/// Errors that may occur when a subprocess fails to run or exit cleanly.
#[derive(Debug)]
pub struct SubprocessError {
/// Meaningful name that can be used to determine what subprocess failed.
pub name: String,
pub err: SubprocessErrorType,
}
impl SubprocessError {
pub fn new(name: String, err: SubprocessErrorType) -> Self {
SubprocessError { name, err }
}
}
/// Error details for `SubprocessError`.
#[derive(Debug)]
pub enum SubprocessErrorType {
/// Subprocess exited with bad exit code.
BadExit(ExitStatus),
/// Could not spawn child process (with `io::Error` as the reason).
Spawn(io::Error),
/// Could not kill child process (with `io::Error` as the reason).
Kill(io::Error),
/// Could not query child process for exit status (with `io::Error` as the reason).
Status(io::Error),
}
/// Manages spawning a subprocess, monitoring it during execution, and killing it when `Xvfb` shuts down.
pub struct SubprocessMonitor {
name: String,
command: Command,
check_subprocess_exit: Option<Box<dyn FnMut(&str, ExitStatus) -> SubprocessResult<()>>>,
on_xvfb_shutdown: Option<Box<dyn FnMut(&mut Child) -> SubprocessResult<()>>>,
}
impl SubprocessMonitor {
/// Creates a new monitor but does not start any subprocess yet.
///
/// This monitor may be configured further by calling mutators like
/// [`SubprocessMonitor::with_subprocess_exit`]. Once configuration is
/// completely, start the subprocess with [`SubprocessMonitor::start`].
pub fn new(name: impl Into<String>, command: Command) -> Self {
SubprocessMonitor {
name: name.into(),
command,
check_subprocess_exit: None,
on_xvfb_shutdown: None,
}
}
/// Sets the callback to execute when [`SubprocessMonitor::check_subprocess_exit`] is called.
pub fn with_subprocess_exit(
&mut self,
f: Box<dyn FnMut(&str, ExitStatus) -> SubprocessResult<()>>,
) -> &mut Self {
self.check_subprocess_exit = Some(f);
self
}
/// Sets the callback to execute when
/// [`SubprocessMonitor::on_xvfb_shutdown`] is called. It may check process
/// status, clean up, etc.
pub fn with_xvfb_shutdown(
&mut self,
f: Box<dyn FnMut(&mut Child) -> SubprocessResult<()>>,
) -> &mut Self {
self.on_xvfb_shutdown = Some(f);
self
}
/// Attempts to spawn a child process from the `Command` provided to
/// [`SubprocessMonitor::new`].
pub fn start(&mut self) -> SubprocessResult<Child> {
self.command
.spawn()
.map_err(|e| SubprocessError::new(self.name.clone(), SubprocessErrorType::Spawn(e)))
}
/// Called when subprocess exit status is polled. This may be used to signal
/// an error because the exit status is bad.
pub fn check_subprocess_exit(&mut self, status: ExitStatus) -> SubprocessResult<()> {
if let Some(f) = &mut self.check_subprocess_exit {
(f)(&self.name, status)
} else {
Ok(())
}
}
/// Called immediately before the parent display server is being shut
/// down. This may be used to send `SIGINT` or otherwise clean up the child
/// before it is killed more forcibly.
pub fn on_xvfb_shutdown(&mut self, child: &mut Child) -> SubprocessResult<()> {
if let Some(f) = &mut self.on_xvfb_shutdown {
(f)(child)
} else {
Ok(())
}
}
/// Returns a callback suitable for passing to [`SubprocessMonitor::check_subprocess_exit`]
/// which requires that the process has exited cleanly.
pub fn require_clean_exit() -> Box<dyn FnMut(&str, ExitStatus) -> SubprocessResult<()>> {
Box::new(|name: &str, status: ExitStatus| {
if status.success() {
Ok(())
} else {
Err(SubprocessError::new(
String::from(name),
SubprocessErrorType::BadExit(status),
))
}
})
}
}
#[cfg(test)]
mod tests {
use crate::headless::DisplayNumberRegistry;
use super::XvfbProcess;
use insta_image::assert_png_snapshot;
use std::{
thread,
time::Duration,
};
/// Starts Xvfb and returns a managing object, or panics.
fn start_xvfb() -> XvfbProcess {
XvfbProcess::start_default(
DisplayNumberRegistry::next_unused_display().expect("cannot find a value for DISPLAY"),
)
.expect("cannot start Xvfb")
}
/// Reads a single frame of PNG data from the PNG image in `data`. Panics on
/// errors.
fn read_png_metadata(data: &[u8]) -> png::OutputInfo {
let mut reader = png::Decoder::new(std::io::Cursor::new(data))
.read_info()
.unwrap();
let mut buf = vec![0; reader.output_buffer_size().unwrap()];
reader.next_frame(&mut buf).unwrap()
}
/// Launches `xeyes(1)` with colors to make sure we're doing the right thing
/// with our color channels when encoding a screenshot tas PNG.
fn xeyes_color(xvfb: &mut XvfbProcess) {
xvfb.run_args("xeyes", "xeyes", |c| {
c.arg("-outline")
.arg("blue")
.arg("-center")
.arg("red")
.arg("-fg")
.arg("green");
})
.unwrap();
thread::sleep(Duration::from_millis(100));
}
#[test]
fn png_blank_screenshot() {
let xvfb = start_xvfb();
let xwd = xvfb.xwd_screenshot().unwrap();
assert_eq!(
xwd.visual_class(),
crate::headless::xwd::VisualClass::TrueColor
);
let args = crate::headless::xvfb::XvfbArgs::default();
assert_eq!(xwd.header.depth, args.depth as u32);
assert_eq!(xwd.header.width, args.width);
assert_eq!(xwd.header.height, args.height);
assert_png_snapshot!("blank_screen", xwd.into_png().unwrap());
}
#[test]
fn png_xeyes_screenshot() {
let mut xvfb = start_xvfb();
xeyes_color(&mut xvfb);
let compressed = xvfb.png_screenshot();
let image_data = read_png_metadata(&compressed);
let args = crate::headless::xvfb::XvfbArgs::default();
assert_eq!(image_data.width, args.width);
assert_eq!(image_data.height, args.height);
assert_png_snapshot!("xeyes", compressed);
}
#[test]
fn png_lamp_image() {
let mut xvfb = start_xvfb();
xvfb.run_args("xwud", "xwud", |c| {
c.arg("-in").arg("src/headless/snowlamp.xwd");
})
.unwrap();
thread::sleep(Duration::from_millis(100));
let compressed = xvfb.png_screenshot();
assert_png_snapshot!("snowlamp_in_window", compressed);
}
}
+325
View File
@@ -0,0 +1,325 @@
//! Provides basic support for rendering dumps of X11 windows made with [the `xwud` utility](https://gitlab.freedesktop.org/xorg/app/xwud/-/blob/master/xwud.c?ref_type=heads).
use std::{
ffi::CString,
io::Cursor,
mem,
};
const SUPPORTED_XWD_VERSION: u32 = 7;
/// Header for `xwd`-style dumps of X11 windows.
///
/// This is taken from `/usr/include/X11/XWDFile.h` on a Debian system.
#[derive(Clone)]
#[repr(C)]
pub struct XwdHeader {
/// Total header size (including null-terminated window name).
pub size: u32,
/// Version of `xwd` that this dump came from.
pub version: u32,
pub _format: u32,
/// Bit depth of the X11 server.
pub depth: u32,
/// Image width.
pub width: u32,
/// Image height.
pub height: u32,
pub _x_offset: u32,
pub _byte_order: u32,
pub _bitmap_unit: u32,
pub _bitmap_bit_order: u32,
pub _bitmap_pad: u32,
pub bits_per_pixel: u32,
pub _bytes_per_line: u32,
/// X11 visual class for this window. See [`XwdImage::visual_class`] for a
/// more human-interpretable value.
pub visual_class: u32,
/// Bitmask for the red channel in pixel data.
pub red_mask: u32,
/// Bitmask for the green channel in pixel data.
pub green_mask: u32,
/// Bitmask for the blue channel in pixel data.
pub blue_mask: u32,
/// Number of bits per RGB tuple in color data. (E.g., 24-bit color may
/// actually be padded to 4 bytes.)
pub bits_per_rgb: u32,
pub _colormap_entries: u32,
/// Number of colors in the colormap for the window that this dump came from.
///
/// Note that a colormap may exist but be unused (if the visual class is not
/// one that uses a colormap).
pub ncolors: u32,
pub _window_width: u32,
pub _window_height: u32,
pub _window_x: u32,
pub _window_y: u32,
pub _window_border_width: u32,
}
const HEADER_SIZE: u32 = mem::size_of::<XwdHeader>() as u32;
/// Color data for an entry in a colormap.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct XwdColor {
pixel: u32,
red: u16,
green: u16,
blue: u16,
flags: u8,
pad: u8,
}
fn next_u32(bytes: &mut impl Iterator<Item = u8>) -> u32 {
let bytes = [
bytes.next().unwrap(),
bytes.next().unwrap(),
bytes.next().unwrap(),
bytes.next().unwrap(),
];
u32::from_be_bytes(bytes)
}
fn next_u16(bytes: &mut impl Iterator<Item = u8>) -> u16 {
let bytes = [bytes.next().unwrap(), bytes.next().unwrap()];
u16::from_be_bytes(bytes)
}
/// Describes how pixel data is represented in an `xwd` dump.
///
/// See descriptions of visual classes at
/// <https://tronche.com/gui/x/xlib/window/visual-types.html> and [the official
/// documentation](https://www.x.org/releases/X11R7.7/doc/xproto/x11protocol.html#visual_information)
/// for more information.
#[derive(Clone, Copy, Eq, Debug, PartialEq)]
pub enum VisualClass {
StaticGray,
GrayScale,
StaticColor,
PseudoColor,
TrueColor,
DirectColor,
}
impl XwdHeader {
/// Decodes an `XwdHeader` from `bytes`, panicking on failure.
///
/// Leaves `bytes` pointing to the start of the window name.
pub fn read(bytes: &mut impl Iterator<Item = u8>) -> XwdHeader {
// This layout might change in the future, but let's just go with it for now.
XwdHeader {
size: next_u32(bytes),
version: next_u32(bytes),
_format: next_u32(bytes),
depth: next_u32(bytes),
width: next_u32(bytes),
height: next_u32(bytes),
_x_offset: next_u32(bytes),
_byte_order: next_u32(bytes),
_bitmap_unit: next_u32(bytes),
_bitmap_bit_order: next_u32(bytes),
_bitmap_pad: next_u32(bytes),
bits_per_pixel: next_u32(bytes),
_bytes_per_line: next_u32(bytes),
visual_class: next_u32(bytes),
red_mask: next_u32(bytes),
green_mask: next_u32(bytes),
blue_mask: next_u32(bytes),
bits_per_rgb: next_u32(bytes),
_colormap_entries: next_u32(bytes),
ncolors: next_u32(bytes),
_window_width: next_u32(bytes),
_window_height: next_u32(bytes),
_window_x: next_u32(bytes),
_window_y: next_u32(bytes),
_window_border_width: next_u32(bytes),
}
}
}
impl XwdColor {
/// Reads an `XwdColor` from `bytes`, panicking on failure.
///
/// Leaves `bytes` pointing to the next byte after the end of the color
/// definition.
pub fn read(bytes: &mut impl Iterator<Item = u8>) -> XwdColor {
XwdColor {
pixel: next_u32(bytes),
red: next_u16(bytes),
green: next_u16(bytes),
blue: next_u16(bytes),
flags: bytes.next().unwrap(),
pad: bytes.next().unwrap(),
}
}
}
/// A decoded `xwd` dump.
pub struct XwdImage {
/// Header with image metadata.
pub header: XwdHeader,
/// Name of the X11 window that the image came from.
pub name: CString,
/// Colormap data for the X11 window that the image came from.
///
/// Note that this may be populated even if it is unused (because the
/// window's visual class is one that does not use a colormap).
pub colors: Vec<XwdColor>,
/// Pixel data for the image.
///
/// This may be indices pointing into `colors`, or it may be actual pixel
/// values.
pub pixels: Vec<u8>,
}
impl XwdImage {
/// Reads an `XwdImage` from `bytes`, panicking on failure.
pub fn read(bytes: &mut impl Iterator<Item = u8>) -> XwdImage {
let header = XwdHeader::read(bytes);
if header.version != SUPPORTED_XWD_VERSION {
panic!(
"header version {} is not supported. only version {} files are supported.",
header.version, SUPPORTED_XWD_VERSION
);
}
let name = Self::read_name(&header, bytes);
let colors = Self::read_colors(&header, bytes);
let pixels = bytes.collect();
XwdImage {
header,
name,
colors,
pixels,
}
}
/// Reads a colormap for the image described by `header` from `bytes`.
///
/// `bytes` must point at the start of the colormap data. Leaves `bytes`
/// pointing at the first byte of the next item in the data stream.
pub fn read_colors(header: &XwdHeader, bytes: &mut impl Iterator<Item = u8>) -> Vec<XwdColor> {
let mut colors = Vec::with_capacity(header.ncolors as usize);
for _ in 0..header.ncolors {
colors.push(XwdColor::read(bytes));
}
colors
}
/// Reads the X11 window name from `bytes`.
///
/// `bytes` must point at the start of the window name. Leaves `bytes`
/// pointing at the first byte of the next item in the data stream.
///
/// Note that the window name is a null-terminated string, but `header.size`
/// (which is supposed to be equal to the size of the header structure plus
/// the window name) may actually be longer than `strlen(name) +
/// sizeof(HeaderType)`. When that is the case, the extra padding after the
/// first null byte in the window name will be passed over silently.
pub fn read_name(header: &XwdHeader, bytes: &mut impl Iterator<Item = u8>) -> CString {
if header.size <= HEADER_SIZE {
panic!(
"Invalid header size {} (smaller than header struct size {})",
header.size, HEADER_SIZE
);
}
let mut buf = Vec::new();
let mut found_nul = false;
for _ in 0..header.size - HEADER_SIZE {
let b = bytes.next().unwrap();
if b == 0 {
if found_nul {
continue;
} else {
buf.push(b);
found_nul = true;
}
} else {
buf.push(b);
}
}
CString::from_vec_with_nul(buf).unwrap()
}
/// Returns the visual class for this window dump.
pub fn visual_class(&self) -> VisualClass {
match self.header.visual_class {
0 => VisualClass::StaticGray,
1 => VisualClass::GrayScale,
2 => VisualClass::StaticColor,
3 => VisualClass::PseudoColor,
4 => VisualClass::TrueColor,
5 => VisualClass::DirectColor,
_ => panic!(
"unrecognized X11 visual class: {}",
self.header.visual_class
),
}
}
/// Encodes this image as a PNG, consumes `self` in the process (because the
/// image data is reused during the encoding process).
///
/// Note that only [`VisualClass::TrueColor`] images with a specific pixel
/// format (24-bit color packed into 32-bit segments with BGR ordering) are
/// supported.
pub fn into_png(self) -> Result<Vec<u8>, png::EncodingError> {
match self.visual_class() {
VisualClass::TrueColor => into_png_true_color(self.header, self.pixels),
_ => todo!("X11 visual classes other than TrueColor are not yet supported"),
}
}
}
fn into_png_true_color(
header: XwdHeader,
mut pixels: Vec<u8>,
) -> Result<Vec<u8>, png::EncodingError> {
if header.depth != 24 {
todo!("pixmap color depths other than 24-bit are not yet supported");
}
if !(header.red_mask == 0xff0000
&& header.green_mask == 0x00ff00
&& header.blue_mask == 0x0000ff)
{
todo!("color orderings other than RGB are not yet supported");
}
if header.bits_per_rgb == 32 {
todo!("bits per rgb triplet other than 32 are not yet supported");
}
let mut out = Cursor::new(Vec::<u8>::new());
let mut encoder = png::Encoder::new(&mut out, header.width, header.height);
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
encoder.set_compression(png::Compression::High);
for pixel in pixels.chunks_mut(4) {
let blue = pixel[0];
let green = pixel[1];
let red = pixel[2];
pixel[0] = red;
pixel[1] = green;
pixel[2] = blue;
pixel[3] = 0xFF;
}
let mut writer = encoder.write_header()?;
writer.write_image_data(&pixels)?;
writer.finish()?;
Ok(out.into_inner())
}
#[cfg(test)]
mod tests {
use super::XwdImage;
use insta_image::assert_png_snapshot;
#[test]
fn png_encode() {
let snowlamp_xwd = XwdImage::read(&mut include_bytes!("snowlamp.xwd").into_iter().copied());
assert_png_snapshot!("snowlamp_encoded", snowlamp_xwd.into_png().unwrap());
}
}
+186
View File
@@ -0,0 +1,186 @@
pub mod logo_data;
pub mod headless;
use headless::{xvfb::XvfbProcess, DisplayNumberRegistry};
use std::{
env,
ffi::{c_char, c_int, CStr, CString},
ptr::{self, NonNull},
sync::Mutex,
};
use wings_rs::WINGsP::{WMHandleEvent, WMInitializeApplication, WMReleaseApplication, WMScreen};
static APPLICATION_COUNT: Mutex<u32> = Mutex::new(0);
/// Stubbed wrapper for WMApplication.
pub struct HeadlessApplication {
/// The headless X server process.
pub xvfb: XvfbProcess,
/// An open client handle to the headless X server.
pub display: NonNull<x11::xlib::Display>,
/// A fully initialized `WMScreen` for an application running on the
/// headless X server.
pub screen: NonNull<WMScreen>,
}
impl HeadlessApplication {
/// Starts a headless X server and calls `WMInitializeApplication` if no
/// WINGs application is active.
pub fn new() -> Self {
static PROGNAME: &'static CStr = c"Test@eqweq_ewq$eqw";
let xvfb = XvfbProcess::start_default(
DisplayNumberRegistry::next_unused_display()
.expect("cannot allocate a value for DISPLAY"),
)
.expect("cannot start Xvfb");
{
let mut application_count = APPLICATION_COUNT.lock().unwrap();
if *application_count == 0 {
unsafe {
let mut argv: Vec<*mut c_char> =
vec![PROGNAME.as_ptr().cast::<c_char>() as *mut c_char];
let mut argc: c_int = 1;
WMInitializeApplication(
PROGNAME.as_ptr().cast::<c_char>(),
&mut argc as *mut _,
argv.as_mut_ptr(),
);
}
}
*application_count += 1;
}
let display_str = CString::new(format!(":{}", xvfb.display())).unwrap();
let display =
NonNull::new(unsafe { x11::xlib::XOpenDisplay(display_str.as_ptr()) }).unwrap();
let screen = WMScreen::new(&display).unwrap();
HeadlessApplication {
xvfb,
display,
screen,
}
}
/// Pumps the WUtil and X11 event queues, hackily.
///
/// Runs WUtil event handlers that should be run by `now`, pumps the X11
/// event queue, dispatches the next pending event (if any), and runs
/// WUtil idle handlers (if no X11 events are available).
///
/// Returns `true` if more events are pending. (This does not account for
/// WUtils timer events that might need to fire, since the future time when
/// `pump_event_queue` will next be called cannot be known.)
///
/// This is somewhat hacky (because it does not match the WINGs main loop
/// logic exactly), so it should only be used by tests.
pub fn pump_event_queue(&mut self, now: std::time::Instant) -> bool {
let display = self.display.as_ptr();
wutil_rs::handlers::with_global_handlers(|handlers| handlers.check_timer_handlers(now));
unsafe {
x11::xlib::XSync(display, 0);
if x11::xlib::XPending(display) > 0 {
let mut event = x11::xlib::XEvent { type_: 0 };
x11::xlib::XNextEvent(display, &mut event as *mut _);
WMHandleEvent(&mut event as *mut _);
} else {
wutil_rs::handlers::run_global_idle_handlers();
}
x11::xlib::XSync(display, 0);
if x11::xlib::XPending(display) > 0 {
return true;
}
wutil_rs::handlers::with_global_handlers(|handlers| handlers.has_idle_handlers())
}
}
}
impl Drop for HeadlessApplication {
fn drop(&mut self) {
unsafe {
x11::xlib::XCloseDisplay(self.display.as_ptr());
// Leak self.screen, since WINGs doesn't provide a cleanup function.
}
let mut application_count = APPLICATION_COUNT.lock().unwrap();
if *application_count <= 1 {
*application_count = 0;
unsafe {
WMReleaseApplication();
}
} else {
*application_count -= 1;
}
self.xvfb.shutdown().unwrap();
}
}
/// Simple wrapper for demo WINGs applications.
///
/// This handles calling `WMInitializeApplication`, which operates on a
/// singleton behind the scenes and wants to be passed command line arguments.
///
/// When the last instantiated `LiveApplication` is dropped,
/// `WMReleaseApplication` is called automatically.
pub struct LiveApplication {
/// Live X11 `Display` that the application is running on.
pub display: NonNull<x11::xlib::Display>,
/// Fully intialized `WMScreen` for the application.
///
/// This is leaked when the `LiveApplication` is dropped because WINGs does
/// not provide a deletion function.
pub screen: NonNull<WMScreen>,
}
impl LiveApplication {
/// Creates a new application wrapper and initializes the global
/// `WMApplication` if necessary.
pub fn new(name: &str) -> Self {
let mut application_count = APPLICATION_COUNT.lock().unwrap();
if *application_count > 0 {
panic!("application already started!");
}
let name = CString::new(name).expect("invalid program name");
unsafe {
let mut argv: Vec<CString> = vec![name];
let name_ptr = argv[0].as_ptr().cast::<c_char>();
for arg in env::args() {
argv.push(CString::new(arg).expect("invalid argument string"));
}
let mut argv_ptrs: Vec<*mut c_char> = argv
.iter_mut()
.map(|a| a.as_ptr().cast::<c_char>() as *mut c_char)
.collect();
let mut argc: c_int = argv_ptrs.len().try_into().expect("invalid argument count");
WMInitializeApplication(name_ptr, &mut argc as *mut _, argv_ptrs.as_mut_ptr());
}
*application_count = 1;
let display = NonNull::new(unsafe { x11::xlib::XOpenDisplay(ptr::null_mut()) })
.expect("could not connect to X11 display");
let screen = WMScreen::new(&display).unwrap();
LiveApplication { display, screen }
}
}
impl Drop for LiveApplication {
fn drop(&mut self) {
unsafe {
x11::xlib::XCloseDisplay(self.display.as_ptr());
// Leak self.screen, since WINGs doesn't provide a cleanup function.
}
let mut application_count = APPLICATION_COUNT.lock().unwrap();
if *application_count <= 1 {
*application_count = 0;
unsafe {
WMReleaseApplication();
}
} else {
*application_count -= 1;
}
}
}
+59
View File
@@ -0,0 +1,59 @@
use std::ffi::CStr;
/// Taken from logo.xpm originally in this repository.
pub static GNUSTEP_XPM: &'static [&'static CStr] = &[
c"45 45 8 1",
c" c None",
c". c #666666666666",
c"X c #777777777777",
c"o c #596559656185",
c"O c #000000000000",
c"+ c #3CF33CF33CF3",
c"@ c #CF3CCF3CCF3C",
c"# c #FFFFFFFFFFFF",
c" ",
c" .Xooooooooo. ",
c" ..ooOOOOOOOOOOOOOo. ",
c" .XoOOOOOOOOOOOOOOOOOO+ ",
c" .oOOOOOOOOOOOOOOOOOOOOOO+ ",
c" XOOOOOOOOOOOOOOOOOOOOOOOOOOo ",
c" oOOOOOOOOOOOOOOOOOOOOOOOOOOOO+ ",
c" oOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO+ooooooo",
c" +OOOOOOOOOOOOOOOOOOOOOOOOoXXXXX.XOOOOOOO",
c" XOOOOOOOOOOOOOOOOOOOOOOOOOX#######.OOOOOO",
c" XOOOOOOOOOOOOOOOOOOOOOOOOOOX########oOOOOO",
c" OOOOOOOOOOOOOOOOOOOOOOOOOOOX#########oOOOO",
c" oOOOOOOOOOOOOOOOOOOOOOOOOOOOX#########@OOOO",
c" OOOOOOOOOOOOOOOOOOOOOOOOOOOOX##########oOOO",
c" oOOOOOOOOOOOOOOOOOOOOOOOOOOOOX##########@OOO",
c" OOOOOOOOOOOOOOOOOOOOOOOOOOOOOX###########+OO",
c" OOOOOOOOOOOOOOOOOOOOOOOOOOOOOX###########oOO",
c"oOOOOOOOOOOOOOOOOOOOOOOOOOOOOOX###########@OO",
c"+OOOOOOOOOOOOOOOOOOOOOOOOOOOOOX###########@OO",
c"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOX############XO",
c"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOX############XO",
c"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOX############XO",
c"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOX############XO",
c"OOOOOOOOOOOOOOOoX.X.X.X.XX.XXX@############XO",
c"OOOOOOOOOOOOOOOX###########################XO",
c"OOOOOOOOOOOOOOOX###########################XO",
c"OOOOOOOOOOOOOOOX###########################XO",
c"+OOOOOOOOOOOOOOX###########################OO",
c"oOOOOOOOOOOOOOOX###########################OO",
c" OOOOOOOOOOOOOOX##########################XOO",
c" OOOOOOOOOOOOOOX##########################oOO",
c" oOOOOOOOOOOOOOX#########################@OOO",
c" OOOOOOOOOOOOOX#########################+OOO",
c" oOOOOOOOOOOOOX########################@OOOO",
c" OOOOOOOOOOOOX########################oOOOO",
c" OOOOOOOOOOOX#######################oOOOOO",
c" OOOOOOOOOOX######################XOOOOOO",
c"OOOOOOOXXXXXXXX@#####################@OOOOOOO",
c"OOOOOOOo############################@OOOOOOOO",
c"OOOOOOOOO@#########################oOOOOOOOOO",
c"OOOOOOOOOOX######################@oOOOOOOOOOO",
c"OOOOOOOOOOOOX###################XOOOOOOOOOOOO",
c"OOOOOOOOOOOOOOX@#############@XOOOOOOOOOOOOOO",
c"OOOOOOOOOOOOOOOOOXXX#####XXXOOOOOOOOOOOOOOOOO",
c"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO",
];
@@ -0,0 +1,14 @@
use insta_image::assert_png_snapshot;
use std::time::Instant;
use wings_rs::font_panel::FontPanel;
use wings_rs_tests::HeadlessApplication;
#[test]
fn show_font_panel() {
let mut app = HeadlessApplication::new();
let mut font_panel =
FontPanel::new(app.screen.as_ptr()).expect("could not construct font panel");
font_panel.show();
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("font_panel", app.xvfb.png_screenshot());
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

@@ -0,0 +1,7 @@
---
source: tests/font_panel_tests.rs
assertion_line: 16
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@@ -0,0 +1,7 @@
---
source: tests/system_pixmap_tests.rs
assertion_line: 265
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,7 @@
---
source: tests/system_pixmap_tests.rs
assertion_line: 109
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -0,0 +1,7 @@
---
source: tests/system_pixmap_tests.rs
assertion_line: 161
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -0,0 +1,7 @@
---
source: tests/system_pixmap_tests.rs
assertion_line: 213
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -0,0 +1,7 @@
---
source: tests/system_pixmap_tests.rs
assertion_line: 317
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,7 @@
---
source: tests/system_pixmap_tests.rs
assertion_line: 291
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,7 @@
---
source: tests/system_pixmap_tests.rs
assertion_line: 135
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,7 @@
---
source: tests/system_pixmap_tests.rs
assertion_line: 187
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,7 @@
---
source: tests/system_pixmap_tests.rs
assertion_line: 239
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,7 @@
---
source: tests/system_pixmap_tests.rs
assertion_line: 57
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -0,0 +1,7 @@
---
source: tests/system_pixmap_tests.rs
assertion_line: 31
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -0,0 +1,7 @@
---
source: tests/system_pixmap_tests.rs
assertion_line: 83
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,7 @@
---
source: tests/wmpixmap_tests.rs
assertion_line: 56
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,7 @@
---
source: tests/wmpixmap_tests.rs
assertion_line: 41
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,7 @@
---
source: tests/wmpixmap_tests.rs
assertion_line: 142
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

@@ -0,0 +1,7 @@
---
source: tests/wmpixmap_tests.rs
assertion_line: 160
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,7 @@
---
source: tests/wmpixmap_tests.rs
assertion_line: 162
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

@@ -0,0 +1,7 @@
---
source: tests/wmpixmap_tests.rs
assertion_line: 58
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

@@ -0,0 +1,7 @@
---
source: tests/wmpixmap_tests.rs
assertion_line: 204
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

@@ -0,0 +1,7 @@
---
source: tests/wmpixmap_tests.rs
assertion_line: 222
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,7 @@
---
source: tests/wmpixmap_tests.rs
assertion_line: 268
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
@@ -0,0 +1,7 @@
---
source: tests/wmpixmap_tests.rs
assertion_line: 286
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
@@ -0,0 +1,7 @@
---
source: tests/wmpixmap_tests.rs
assertion_line: 304
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

@@ -0,0 +1,7 @@
---
source: tests/wmpixmap_tests.rs
assertion_line: 220
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,7 @@
---
source: tests/wmpixmap_tests.rs
assertion_line: 130
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

@@ -0,0 +1,7 @@
---
source: tests/wmpixmap_tests.rs
assertion_line: 229
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

@@ -0,0 +1,7 @@
---
source: tests/wmpixmap_tests.rs
assertion_line: 451
expression: app.xvfb.png_screenshot()
extension: png
snapshot_kind: binary
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -0,0 +1,355 @@
use insta_image::assert_png_snapshot;
use std::time::Instant;
use wings_rs::{
pixmap::ffi::*,
WINGsP,
};
use wings_rs_tests::HeadlessApplication;
#[test]
fn draw_return_arrow() {
let mut app = HeadlessApplication::new();
unsafe {
let screen = &raw mut *app.screen.as_ptr();
let display = (*screen).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = WMGetSystemPixmap(screen, WINGsP::constants::WSystemIcon_ReturnArrow);
WMDrawPixmap(pixmap, win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("return_arrow", app.xvfb.png_screenshot());
x11::xlib::XUnmapWindow(display, win);
WMReleasePixmap(pixmap);
}
}
#[test]
fn draw_highlighted_return_arrow() {
let mut app = HeadlessApplication::new();
unsafe {
let screen = &raw mut *app.screen.as_ptr();
let display = (*screen).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = WMGetSystemPixmap(screen, WINGsP::constants::WSystemIcon_HighlightedReturnArrow);
WMDrawPixmap(pixmap, win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("highlighted_return_arrow", app.xvfb.png_screenshot());
x11::xlib::XUnmapWindow(display, win);
WMReleasePixmap(pixmap);
}
}
#[test]
fn draw_scroller_dimple() {
let mut app = HeadlessApplication::new();
unsafe {
let screen = &raw mut *app.screen.as_ptr();
let display = (*screen).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = WMGetSystemPixmap(screen, WINGsP::constants::WSystemIcon_ScrollerDimple);
WMDrawPixmap(pixmap, win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("scroller_dimple", app.xvfb.png_screenshot());
x11::xlib::XUnmapWindow(display, win);
WMReleasePixmap(pixmap);
}
}
#[test]
fn draw_arrow_left() {
let mut app = HeadlessApplication::new();
unsafe {
let screen = &raw mut *app.screen.as_ptr();
let display = (*screen).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = WMGetSystemPixmap(screen, WINGsP::constants::WSystemIcon_ArrowLeft);
WMDrawPixmap(pixmap, win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("arrow_left", app.xvfb.png_screenshot());
x11::xlib::XUnmapWindow(display, win);
WMReleasePixmap(pixmap);
}
}
#[test]
fn draw_highlighted_arrow_left() {
let mut app = HeadlessApplication::new();
unsafe {
let screen = &raw mut *app.screen.as_ptr();
let display = (*screen).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = WMGetSystemPixmap(screen, WINGsP::constants::WSystemIcon_HighlightedArrowLeft);
WMDrawPixmap(pixmap, win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("highlighted_arrow_left", app.xvfb.png_screenshot());
x11::xlib::XUnmapWindow(display, win);
WMReleasePixmap(pixmap);
}
}
#[test]
fn draw_arrow_right() {
let mut app = HeadlessApplication::new();
unsafe {
let screen = &raw mut *app.screen.as_ptr();
let display = (*screen).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = WMGetSystemPixmap(screen, WINGsP::constants::WSystemIcon_ArrowRight);
WMDrawPixmap(pixmap, win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("arrow_right", app.xvfb.png_screenshot());
x11::xlib::XUnmapWindow(display, win);
WMReleasePixmap(pixmap);
}
}
#[test]
fn draw_highlighted_arrow_right() {
let mut app = HeadlessApplication::new();
unsafe {
let screen = &raw mut *app.screen.as_ptr();
let display = (*screen).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = WMGetSystemPixmap(screen, WINGsP::constants::WSystemIcon_HighlightedArrowRight);
WMDrawPixmap(pixmap, win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("highlighted_arrow_right", app.xvfb.png_screenshot());
x11::xlib::XUnmapWindow(display, win);
WMReleasePixmap(pixmap);
}
}
#[test]
fn draw_arrow_up() {
let mut app = HeadlessApplication::new();
unsafe {
let screen = &raw mut *app.screen.as_ptr();
let display = (*screen).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = WMGetSystemPixmap(screen, WINGsP::constants::WSystemIcon_ArrowUp);
WMDrawPixmap(pixmap, win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("arrow_up", app.xvfb.png_screenshot());
x11::xlib::XUnmapWindow(display, win);
WMReleasePixmap(pixmap);
}
}
#[test]
fn draw_highlighted_arrow_up() {
let mut app = HeadlessApplication::new();
unsafe {
let screen = &raw mut *app.screen.as_ptr();
let display = (*screen).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = WMGetSystemPixmap(screen, WINGsP::constants::WSystemIcon_HighlightedArrowUp);
WMDrawPixmap(pixmap, win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("highlighted_arrow_up", app.xvfb.png_screenshot());
x11::xlib::XUnmapWindow(display, win);
WMReleasePixmap(pixmap);
}
}
#[test]
fn draw_arrow_down() {
let mut app = HeadlessApplication::new();
unsafe {
let screen = &raw mut *app.screen.as_ptr();
let display = (*screen).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = WMGetSystemPixmap(screen, WINGsP::constants::WSystemIcon_ArrowDown);
WMDrawPixmap(pixmap, win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("arrow_down", app.xvfb.png_screenshot());
x11::xlib::XUnmapWindow(display, win);
WMReleasePixmap(pixmap);
}
}
#[test]
fn draw_highlighted_arrow_down() {
let mut app = HeadlessApplication::new();
unsafe {
let screen = &raw mut *app.screen.as_ptr();
let display = (*screen).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = WMGetSystemPixmap(screen, WINGsP::constants::WSystemIcon_HighlightedArrowDown);
WMDrawPixmap(pixmap, win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("highlighted_arrow_down", app.xvfb.png_screenshot());
x11::xlib::XUnmapWindow(display, win);
WMReleasePixmap(pixmap);
}
}
#[test]
fn draw_check_mark() {
let mut app = HeadlessApplication::new();
unsafe {
let screen = &raw mut *app.screen.as_ptr();
let display = (*screen).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = WMGetSystemPixmap(screen, WINGsP::constants::WSystemIcon_CheckMark);
WMDrawPixmap(pixmap, win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("check_mark", app.xvfb.png_screenshot());
x11::xlib::XUnmapWindow(display, win);
WMReleasePixmap(pixmap);
}
}
@@ -0,0 +1,459 @@
use insta_image::assert_png_snapshot;
use std::{
ffi::c_char,
ptr::{self, NonNull},
time::Instant,
};
use wings_rs::pixmap::ffi::*;
use wings_rs_tests::HeadlessApplication;
use wrlib_rs::ffi::{RLoadImage, RReleaseImage};
#[test]
fn draw_blank_pixmap() {
let mut app = HeadlessApplication::new();
unsafe {
let display = (*app.screen.as_ptr()).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = NonNull::new(WMCreatePixmap(app.screen.as_ptr(), 128, 196, 24, 1)).unwrap();
WMDrawPixmap(pixmap.as_ptr(), win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("empty_window", app.xvfb.png_screenshot());
let gc = x11::xlib::XCreateGC(display, (*pixmap.as_ptr()).pixmap(), 0, ptr::null_mut());
x11::xlib::XSetForeground(display, gc, 0);
x11::xlib::XFillRectangle(display, (*pixmap.as_ptr()).pixmap(), gc, 0, 0, 128, 196);
WMDrawPixmap(pixmap.as_ptr(), win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("empty_pixmap_on_window", app.xvfb.png_screenshot());
x11::xlib::XFreeGC(display, gc);
WMReleasePixmap(pixmap.as_ptr());
}
}
// WMCreatePixmapFromXPixmaps is not tested because it simply fills in struct
// fields. Coverage from other tests in this file should be adequate.
#[test]
fn draw_pixmap_from_file() {
let mut app = HeadlessApplication::new();
unsafe {
let display = (*app.screen.as_ptr()).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = NonNull::new(WMCreatePixmapFromFile(
app.screen.as_ptr(),
c"tests/image_128x120.png".as_ptr(),
))
.unwrap();
WMDrawPixmap(pixmap.as_ptr(), win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("from_file_pixmap", app.xvfb.png_screenshot());
WMReleasePixmap(pixmap.as_ptr());
}
}
#[test]
fn draw_pixmap_from_r_image_no_mask() {
let mut app = HeadlessApplication::new();
unsafe {
let display = (*app.screen.as_ptr()).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let rimage = RLoadImage(
(*app.screen.as_ptr()).rcontext,
c"tests/image_128x120.png".as_ptr(),
0,
);
let pixmap =
NonNull::new(WMCreatePixmapFromRImage(app.screen.as_ptr(), rimage, 255)).unwrap();
WMDrawPixmap(pixmap.as_ptr(), win, 70, 74);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("from_r_image_no_mask", app.xvfb.png_screenshot());
WMReleasePixmap(pixmap.as_ptr());
}
}
#[test]
fn draw_pixmap_from_r_image_with_mask() {
let mut app = HeadlessApplication::new();
unsafe {
let display = (*app.screen.as_ptr()).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XBlackPixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let rimage = NonNull::new(RLoadImage(
(*app.screen.as_ptr()).rcontext,
c"tests/image_128x120_varying_alpha.png".as_ptr(),
255,
))
.unwrap();
// The threshold given to WMCreatePixmapFromRImage is compared against
// the image's alpha channel. Pixels whose transparency are below the
// threshold are masked out. This should draw a quartet of images with
// varying masks.
// Totally masked.
let pixmap_255 = NonNull::new(WMCreatePixmapFromRImage(
app.screen.as_ptr(),
rimage.as_ptr(),
255,
))
.unwrap();
WMDrawPixmap(pixmap_255.as_ptr(), win, 70, 74);
// Three bubbles masked.
let pixmap_200 = NonNull::new(WMCreatePixmapFromRImage(
app.screen.as_ptr(),
rimage.as_ptr(),
200,
))
.unwrap();
WMDrawPixmap(pixmap_200.as_ptr(), win, 200, 74);
// Two bubbles masked.
let pixmap_128 = NonNull::new(WMCreatePixmapFromRImage(
app.screen.as_ptr(),
rimage.as_ptr(),
128,
))
.unwrap();
WMDrawPixmap(pixmap_128.as_ptr(), win, 70, 200);
// Nothing masked.
let pixmap_0 = NonNull::new(WMCreatePixmapFromRImage(
app.screen.as_ptr(),
rimage.as_ptr(),
0,
))
.unwrap();
WMDrawPixmap(pixmap_0.as_ptr(), win, 200, 200);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("from_r_image_with_mask", app.xvfb.png_screenshot());
WMReleasePixmap(pixmap_255.as_ptr());
WMReleasePixmap(pixmap_200.as_ptr());
WMReleasePixmap(pixmap_128.as_ptr());
WMReleasePixmap(pixmap_0.as_ptr());
RReleaseImage(rimage.as_ptr());
}
}
#[test]
fn draw_blended_pixmap_from_r_image() {
let mut app = HeadlessApplication::new();
unsafe {
let display = (*app.screen.as_ptr()).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let rimage = RLoadImage(
(*app.screen.as_ptr()).rcontext,
c"tests/image_128x120.png".as_ptr(),
0,
);
let pixmap =
NonNull::new(WMCreateGreyedPixmapFromRImage(app.screen.as_ptr(), rimage)).unwrap();
WMDrawPixmap(pixmap.as_ptr(), win, 70, 74);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("from_r_image_no_mask", app.xvfb.png_screenshot());
WMReleasePixmap(pixmap.as_ptr());
}
}
#[test]
fn draw_blended_pixmap_from_file() {
let mut app = HeadlessApplication::new();
unsafe {
let display = (*app.screen.as_ptr()).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = NonNull::new(WMCreatePixmapFromFile(
app.screen.as_ptr(),
c"tests/image_with_transparent_dot_128x120.png".as_ptr(),
))
.unwrap();
WMDrawPixmap(pixmap.as_ptr(), win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("from_file_blended_base_pixmap", app.xvfb.png_screenshot());
x11::xlib::XUnmapWindow(display, win);
WMReleasePixmap(pixmap.as_ptr());
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!(
"from_file_blended_cleared_window",
app.xvfb.png_screenshot()
);
let pixmap = NonNull::new(WMCreateGreyedPixmapFromFile(
app.screen.as_ptr(),
c"tests/image_with_transparent_dot_128x120.png".as_ptr(),
))
.unwrap();
WMDrawPixmap(pixmap.as_ptr(), win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!("from_file_blended_with_grey", app.xvfb.png_screenshot());
WMReleasePixmap(pixmap.as_ptr());
}
}
#[test]
fn draw_scaled_blended_pixmap_from_file() {
let mut app = HeadlessApplication::new();
unsafe {
let display = (*app.screen.as_ptr()).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = NonNull::new(WMCreatePixmapFromFile(
app.screen.as_ptr(),
c"tests/image_with_transparent_dot_128x120.png".as_ptr(),
))
.unwrap();
WMDrawPixmap(pixmap.as_ptr(), win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!(
"from_file_scaled_blended_base_pixmap",
app.xvfb.png_screenshot()
);
x11::xlib::XUnmapWindow(display, win);
WMReleasePixmap(pixmap.as_ptr());
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!(
"from_file_scaled_blended_cleared_window",
app.xvfb.png_screenshot()
);
let pixmap = NonNull::new(WMCreateScaledGreyedPixmapFromFile(
app.screen.as_ptr(),
c"tests/image_with_transparent_dot_128x120.png".as_ptr(),
60,
64,
))
.unwrap();
WMDrawPixmap(pixmap.as_ptr(), win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!(
"from_file_scaled_blended_with_grey",
app.xvfb.png_screenshot()
);
WMReleasePixmap(pixmap.as_ptr());
}
}
#[test]
fn draw_scaled_blended_pixmap_from_file_non_integral_scaling() {
let mut app = HeadlessApplication::new();
unsafe {
let display = (*app.screen.as_ptr()).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = NonNull::new(WMCreatePixmapFromFile(
app.screen.as_ptr(),
c"tests/image_128x120.png".as_ptr(),
))
.unwrap();
WMDrawPixmap(pixmap.as_ptr(), win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!(
"from_file_scaled_blended_non_integral_base_pixmap",
app.xvfb.png_screenshot()
);
x11::xlib::XUnmapWindow(display, win);
WMReleasePixmap(pixmap.as_ptr());
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!(
"from_file_scaled_blended_non_integral_cleared_window",
app.xvfb.png_screenshot()
);
let pixmap = NonNull::new(WMCreateScaledGreyedPixmapFromFile(
app.screen.as_ptr(),
// No alpha channel on this image, so no color blending happens.
c"tests/image_128x120.png".as_ptr(),
// Shrink by only a little bit, such that simple integer division
// might be thrown off. This test ensures that rescaling will
// actually happen.
110,
114,
))
.unwrap();
WMDrawPixmap(pixmap.as_ptr(), win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!(
"from_file_scaled_blended_non_integral_scaled",
app.xvfb.png_screenshot()
);
WMReleasePixmap(pixmap.as_ptr());
}
}
#[test]
fn draw_pixmap_from_xpm_data() {
let mut app = HeadlessApplication::new();
let mut data: Vec<*mut c_char> = wings_rs_tests::logo_data::GNUSTEP_XPM
.iter()
.map(|line| line.as_ptr() as *mut c_char)
.collect();
unsafe {
let display = (*app.screen.as_ptr()).display;
let win = x11::xlib::XCreateSimpleWindow(
display,
(*app.screen.as_ptr()).rootWin,
0,
0,
512,
512,
1,
1,
x11::xlib::XWhitePixel(display, 0),
);
x11::xlib::XMapWindow(display, win);
let pixmap = NonNull::new(WMCreatePixmapFromXPMData(
app.screen.as_ptr(),
data.as_mut_ptr(),
)).unwrap();
WMDrawPixmap(pixmap.as_ptr(), win, 64, 96);
while app.pump_event_queue(Instant::now()) {}
assert_png_snapshot!(
"pixmap_from_xpm_data",
app.xvfb.png_screenshot()
);
x11::xlib::XUnmapWindow(display, win);
WMReleasePixmap(pixmap.as_ptr());
}
}

Some files were not shown because too many files have changed in this diff Show More