1
0
mirror of https://github.com/gryf/wmaker.git synced 2026-04-26 19:01:25 +02:00

Compare commits

...

18 Commits

Author SHA1 Message Date
David Maciejak 931186bd18 wmaker: prevent transient windows to create an appicon
This patch is preventing transient windows which are defined as
short-lived window that "belongs" to a main application window
to create their own appicon.
Some applications are not creating an appicon but are getting
default appicon for their popup windows which can only disappear
when wmaker is restarted.
2026-04-14 19:31:41 +01:00
David Maciejak 5b631cad93 wmaker: fix EmulateAppIcon index from advanced_option
This patch is fixing the EmulateAppIcon index which should be
moreChk[8], moreChk[7] used until now is for DontSaveSession
attribute.
2026-04-06 23:47:47 +01:00
David Maciejak ee9fd77a56 wmaker: fix crash for windows without WM_HINTS
This patch is adding a guard before calling wm_hints->icon_pixmap.
Crash can be reproduced with decoration-less apps when disabling
"Emulate application icon" attribute.

Ref:

Program terminated with signal SIGSEGV, Segmentation fault.
806 if (!getSize(wwin->wm_hints->icon_pixmap, &w, &h, &d)) {
[Current thread is 1 (Thread 0x7ffff660ed40 (LWP 29297))]
2026-04-06 23:47:47 +01:00
David Maciejak 62e341b5cc wmaker: add test for _NET_WM_FULLSCREEN_MONITORS
This patch is adding a small test app to validate that
_NET_WM_FULLSCREEN_MONITORS is working as expected.
2026-04-05 21:47:05 +01:00
David Maciejak fa49b7c003 WINGs: avoid splitting UTF-8 characters when wrapping text
This patch is to split UTF-8 word by characters and not bytes.
Issue reported at https://github.com/window-maker/wmaker/issues/65
2026-04-04 21:35:36 +01:00
David Maciejak 6dbc6c95ab WINGs: make UTF-8 functions global
This patch is moving the existing UTF-8 functions from wtextfield.c
to WINGsP.h.in to be available to all modules.
2026-04-04 21:35:36 +01:00
David Maciejak 4babc0e422 Doc: fix typos in NEWS and README 2026-04-04 15:41:07 +01:00
David Maciejak 33fba87ed1 wmaker: add directional window focus
This patch is adding directional window focus
(like in openbox), where the focus can be passed to
the current window neighboors with a shortcut key.
Comparisons are performed using window centres.

New options are FocusWindowLeftKey, FocusWindowRightKey,
FocusWindowUpKey, FocusWindowDownKey.
Other WM are setting those to SUPER+LEFT/RIGHT/UP/DOWN keys
but currently those are not set by default.

CirculateRaise option which is described in WPrefs as
'Raise window when switching focus with keyboard' is also
used to raise/not raise windows that are fully covered by
other windows.
2026-04-03 10:35:39 +01:00
David Maciejak a3c02a22bd NEWS: add new feature descriptions 2026-04-03 10:35:39 +01:00
David Maciejak 0d74066aea wmaker: treat _NET_WM_WINDOW_TYPE property as a list
This patch is treating the _NET_WM_WINDOW_TYPE property as an array
of atoms as defined in X11. Currently, we are only getting the first one,
while the client can set multiple window types ordered from most specific
to least specific.
2026-04-02 08:57:25 +01:00
David Maciejak 26a296db23 wmaker: dynamic vim-like window marking feature
This patch is adding vim-like window marking, like in i3.
A window can be dynamically assigned a mark label.
Then a marked window can be pulled or jumped to.
Or the current focused window can be swapped with a marked window.

The mark label appears in the Window List in between the window
title and the workspace name.

Those new options in WindowMaker conf file are used to control
the actions: MarkSetKey, MarkUnsetKey, MarkBringKey, MarkJumpKey
and MarkSwapKey.

Those actions are set to None by default.
2026-04-02 08:57:25 +01:00
David Maciejak ac1fa7fc6d wmaker: fix client repositioning on frame moves
This patch is preventing X server gravity adjustments from offsetting
the client windows esp when the gravity is non standard as seen
in openoffice with xprop: 'window gravity: Static'.
Also fixing _NET_WM_STATE property query request as in X11 it can
contain multiple atoms simultaneously, acting as an array.
2026-03-29 20:05:17 +01:00
David Maciejak 1f03c13f4d wmaker: improve support for RandR
This patch is improving the support for RandR.
It uses version 1.3 released in March 2009.
Most of the support is done in randr.c/randr.h
It is built on top of the mature Xinerama structure
but Xinerama lib is not required.
Like for Xinerama, RandR is now auto enabled if the
library is found at compiled time.

RandR support can be used in 2 modes:
A static mode (which is the default) is to define manually
your setup with external tools like xrandr or arandr,
like for example what Openbox is doing.

A dynamic mode, which is triggered on hotplug events,
like for example what GNOME is doing.
If a new monitor is detected, it will select the best mode
available and add it to the right on the existing monitors.

The mode can be switched with a new option available in WindowMaker
conf file (or via WPrefs expert panel):

HotplugMonitor = NO;
2026-03-28 10:50:23 +00:00
David Maciejak 8e84264036 WPrefs: add hotplug monitor option in expert panel
This patch is adding a new HotplugMonitor option
to automatically (de)activate monitors on randr hotplug events.
It is disabled by default.
2026-03-28 10:50:23 +00:00
David Maciejak 4b4abf4c50 wmaker: update callback function checks
This patch is adding the check for the getString function
that was added in commit be495bedbc.
2026-03-27 23:30:05 +00:00
David Maciejak a631e3060e wmaker: remove apercu struct check artefact
This patch is removing the check for the legacy_minipreview_config struct
which was removed in commit fa8121ee61
Without it we can see the error below when running make check:

Error: structure "legacy_minipreview_config" was not found in ./defaults.c
2026-03-27 23:30:05 +00:00
David Maciejak 6c5c3e6181 wmaker: fix wire frame drawing when snapping
This patch is fixing the wire frame dimension which
was computed wrongly when FrameBorderWidth was set
and the window snapped top half or bottom half.
In such case, drawTransparentFrame was passed the
width and height of the screen while it should have
used an inner frame size (meaning without frame border).
The result bug was that the wire frame width was too large
and the right edge displayed out of the monitor head.
2026-03-27 23:30:05 +00:00
David Maciejak 329f82f6e7 WPrefs: set the PID of the application to the window
This patch is setting the _NET_WM_PID atom for WPrefs
to report its PID.

Before the patch, the PID reported is unset (0)
$ wmctrl -lp|grep 'Window Maker Preferences'
0x00a000b0  0 0      Linux Window Maker Preferences

After the patch, the PID is set (20110 in the example below)
0x00a000b0  0 20110  Linux Window Maker Preference
2026-03-23 20:43:42 +00:00
38 changed files with 1713 additions and 165 deletions
+199 -26
View File
@@ -1,10 +1,183 @@
NEWS for veteran Window Maker users
-----------------------------------
-- 0.97.0
Directional window focus
------------------------
The keyboard focus can now be moved in a specific direction (left, right,
up, down) relative to the currently focused window, using window centres
for comparison. The shortcuts can be configured in WPrefs under
"Keyboard Shortcuts", or set directly in ~/GNUstep/Defaults/WindowMaker
via "FocusWindowLeftKey", "FocusWindowRightKey", "FocusWindowUpKey" and
"FocusWindowDownKey" (disabled by default).
The existing "CirculateRaise" option ("Raise window when switching focus
with keyboard" in WPrefs) also controls whether windows that are fully
obscured by others are raised when receiving focus this way.
Vim-like window marking
-----------------------
Windows can now be dynamically assigned a mark label (a key or
modifier+key combination) allowing them to be instantly recalled without
searching. This is inspired by vim's mark-and-jump navigation.
The mark label is displayed in the window list menu between the window
title and the workspace name.
Five actions can be configured in ~/GNUstep/Defaults/WindowMaker
(or in WPrefs under "Keyboard Shortcuts")
"MarkSetKey": assign a label to the focused window (e.g. with the
action bound to "Super+M", pressing "Super+M x" marks the focused
window with label 'x').
"MarkUnsetKey": remove the mark from a labeled window.
"MarkJumpKey": focus and raise the window with the given label.
"MarkBringKey": move the marked window to the current workspace and
focus it.
"MarkSwapKey": swap the focused window with the marked window.
Those are disabled by default.
Marks are persistent across sessions and saved in the WMState file.
Multi-key and sticky-chain keybindings
--------------------------------------
Keyboard shortcuts now support multi-key sequences, similar to
Emacs-style prefix bindings (e.g. pressing a leader key followed by
one or more continuation keys).
An optional sticky-chain mode allows a prefix key to remain active
until a cancel key is pressed or a timeout expires, making it possible
to trigger multiple actions in sequence without re-entering the prefix.
New options in ~/GNUstep/Defaults/WindowMaker:
"KeychainTimeoutDelay" is the timeout in milliseconds after which an
active chain expires. Default is 500. Set to 0 to disable the
timeout.
"KeychainCancelKey" sets a key to explicitly cancel an active chain.
(disabled by default).
Configurable modifier key labels
---------------------------------
A new "ModifierKeyShortLabels" option in ~/GNUstep/Defaults/WindowMaker
allows customizing the labels shown for modifier keys in menus and
keyboard shortcut displays. Unicode symbols can be used in place of the
default text labels. For example to use the same symbol as defined on
MacOS:
ModifierKeyShortLabels = (
"\342\207\247",
"\342\214\203",
"\342\214\245",
"\342\207\255",
"\342\207\263",
"\342\214\230",
"\342\207\252",
"\342\227\206",
"\342\214\245"
);
Configurable mouse wheel focus behavior
----------------------------------------
Scrolling the mouse wheel over a window can now optionally move the
keyboard focus to that window. The option is available in WPrefs under
"Expert User Preferences", or by setting "MouseWheelFocusEnable" in
~/GNUstep/Defaults/WindowMaker (NO by default).
Application icons in the window list
-------------------------------------
Application icons can now be shown in the window list menu. This behavior
can be configured by setting the "WindowListAppIcons" option in
~/GNUstep/Defaults/WindowMaker or "Show app icons in window list." in
WPrefs under "Expert User Preferences" (NO by default).
Configurable screenshot filename template
-----------------------------------------
The filename (strftime compatible) format used when saving screenshots
can now be customized by setting the "ScreenshotFileNameTemplate"
option in ~/GNUstep/Defaults/WindowMaker. The default remains the
format introduced in 0.96.0.
JPEG XL image support
---------------------
WRaster can now optionally load JPEG XL images if the libjxl
library is present at build time. The feature can be toggled at
configure time with --enable-jxl/--disable-jxl.
wmiv: archive files and unknown format handling
-----------------------------------------------
wmiv can now open images stored inside archive files (with libarchive).
A new option to silently skip unknown image formats is also available.
It supports ctrl+c shortcut to copy the current image to the clipboard.
Support for _NET_WM_MOVERESIZE
------------------------------
Window Maker now handles the _NET_WM_MOVERESIZE EWMH message, which
allows applications to initiate interactive window moves and resizes
through a standard protocol request (for those applications without
decorations: VS Code, Google Chrome, Steam and Discord, ...).
Cycle windows across all workspaces
-----------------------------------
A new 'Cycle all windows from all workspaces' expert option allows
the Alt-Tab window switcher to include windows from all workspaces,
not only the current one. Which is the "CycleAllWorkspaces" option
in ~/GNUstep/Defaults/WindowMaker (NO by default).
RandR and multi-monitor improvements
------------------------------------
RandR version >= 1.3 is automatically enabled when detected. By default,
it operates in static mode, requiring the screen layout to be defined
manually. A new "HotplugMonitor" option in ~/GNUstep/Defaults/WindowMaker
(NO by default) enables automatic monitor detection on hotplug events,
adding newly connected monitors to the right of the existing layout.
This option is also available in WPrefs expert panel under
"Automatically (de)activate monitors on hotplug events".
Titlebar language button revamp
-------------------------------
When the modelock feature is enabled, the legacy hardcoded language
dropdown icons in the titlebar are replaced by a compact button
displaying the short name of the current XKB locale (those are
basically the layouts displayed via setxkbmap -query).
Clicking the button cycles through up to four keyboard layouts
(matching the four XKB groups). The xkbfile library is required for
this feature.
-- 0.96.0
Hot Corners feature
--------------------------
-------------------
Screen corners can be assigned an external command to be
executed when the mouse pointer is entering those areas.
@@ -103,12 +276,12 @@ file.
Alternative way for traverse half-maximized windows
---------------------------------------------------
For now, there could be three possible state of the window while using
half-maximized feature. Unmaximized window have its state saved during that
process, which was use to unmaximize it. For example, if there is a window,
which is maximized on the half left side of the screen, and while requesting
left-half-maximized, it become unmaximized with size and dimension restored to
original state.
For now, there could be three possible states of the window while using
the half-maximized feature. Unmaximized windows have their state saved during
that process, which is used to unmaximize them. For example, if there is a
window that is maximized on the left half of the screen, and while requesting
left-half-maximize it becomes unmaximized with its size and dimensions
restored to the original state.
By setting "AlternativeHalfMaximized" option to "Yes"
~/GNUstep/Defaults/WindowMaker config file (or by using WPrefs.app and option
@@ -136,9 +309,9 @@ Window can be moved using keyboard shortcut right-half-maximize:
├┬┐ └──────────┘│
└┴┴────────────────────┘
Further invoking right-half-maximize will do nothing. Note, that window always
can be unmaximzied using appropriate keyboard shortcut or by selecting
"Unmaximize" from window menu.
Further invoking right-half-maximize will do nothing. Note, that the window always
can be unmaximized using an appropriate keyboard shortcut or by selecting
"Unmaximize" from the window menu.
Going to opposite direction by invoking left-half-maximize, will make the window
maximized in both, vertical and horizontal directions:
@@ -229,8 +402,8 @@ left and second display is on the right with window on first screen:
├┬┐ ├┬┬┐ │
└┴┴───────────────┴┴┴┴───────────────┘
It can be right-half-maximized (using previously defined key combination), so it
become:
It can be right-half-maximized (using the previously defined key combination), so it
becomes:
┌┬───────┬────────┬─────────────────┬┐
├┘ ├────────┤ ├┤
@@ -242,7 +415,7 @@ become:
└┴┴───────────────┴┴┴┴───────────────┘
In this example there is an assumption that WindowMaker is configured, that
maximized windows wont cover mini icons nor dock.
maximized windows won't cover mini icons nor the dock.
Without setting new option to true, issuing another right-half-maximize will
restore window dimensions and position to the original state (like on the first
@@ -257,7 +430,7 @@ figure). With new option set to true it will move window to second screen like:
├┬┐ ├┬┬┬─────┘ │
└┴┴───────────────┴┴┴┴───────────────┘
Another activation of right-half-maxmimize:
Another activation of right-half-maximize:
┌┬────────────────┬────────┬────────┬┐
├┘ │ ├────────┼┤
@@ -268,7 +441,7 @@ Another activation of right-half-maxmimize:
├┬┐ ├┬┬┐ └────────┘│
└┴┴───────────────┴┴┴┴───────────────┘
And final activation of right-half-maxmimize:
And final activation of right-half-maximize:
┌┬────────────────┬───────┬─────────┬┐
├┘ ├───────┤ ├┤
@@ -279,8 +452,8 @@ And final activation of right-half-maxmimize:
├┬┐ ├┬┬┐ │
└┴┴───────────────┴┴┴┴───────────────┘
Where window is restored its size (but not position, since it it on different
head now). Moving window in directions like left-half-maximize,
Where the window is restored to its size (but not position, since it is on a different
head now). Moving a window in directions like left-half-maximize,
top-half-maximize and bottom-half-maximize will behave in similar way depending
on the layout of displays.
@@ -352,12 +525,12 @@ There are four choices:
geometry is restored.
* "Unmaximize" ("...consider the window unmaximized") causes a maximized window
to be moved when dragged and remains partially maximized, i.e., it keeps its
maximized geometry, but is consider to be unmaximized. In particular, it can
maximized geometry, but is considered to be unmaximized. In particular, it can
be immediately re-maximized.
* "NoMove" ("...do not move the window") prevents a maximized window from being
moved when dragged.
Note that, to accomodate this option in the "Window Handling Preferences" tab in
Note that, to accommodate this option in the "Window Handling Preferences" tab in
WPrefs.app, the option to "Open dialogs in the same workspace as their owners"
(which sets the "OpenTransientOnOwnerWorkspace" option from
~/GNUstep/Defaults/WindowMaker) has been moved to "Expert User Preferences".
@@ -1157,7 +1330,7 @@ Others
- added DONT_SCALE_ICONS compile time option
- added --dont-restore cmd line option. When passed to wmaker, it
wont restore the state saved previously.
won't restore the state saved previously.
--- 0.61.1
@@ -1697,10 +1870,10 @@ Clip icons when the mouse pointer enter/leave the Clip. To avoid unwanted
raising/lowering there is a time threshold before raising/lowering.
The thresholds can be changed in wconfig.h by changing one or both of
AUTO_LOWER_DELAY and AUTO_RAISE_DELAY (expressed in milliseconds).
For example if you set AUTO_RAISE_DELAY to 0, then the Clip will be raised as
For example, if you set AUTO_RAISE_DELAY to 0, then the Clip will be raised as
soon as the mouse pointer enters its area. Setting AUTO_RAISE_DELAY to a very
big value, will make the Clip to practically do not auto raise unless clicked,
but to be automatically lowered after AUTO_LOWER_DELAY (ms) when leaved.
big value will make the Clip practically not auto-raise unless clicked,
but it will still be automatically lowered after AUTO_LOWER_DELAY (ms) when left.
New ThemePack Format
@@ -2467,7 +2640,7 @@ applets of that kind, because the icon window do not belongs to WindowMaker,
but to the application itself. For such icons you must keep the mouse over the
icon groundplate, else it will collapse back. But usually these type of icons
will not stay in a collapsed clip, since one needs to see what they show.
When the clip is leaved it will collapse back after a delay of 1 second, unless
When the clip is left it will collapse back after a delay of 1 second, unless
the mouse is moved back to the clip area. The delay can be set modifying
AUTO_COLLAPSE_DELAY in wconfig.h. Default is 1000 (ms) which means 1 second.
@@ -2804,8 +2977,8 @@ When the step to move becomes smaller than IconSlideStep, IconSlideStep will
be used further until the destination is reached.
This way a nice deceleration effect is achieved, since while the icon
approaches destination, it will use smaller steps, gibing the illusion that
the icons is moving slower.
approaches the destination, it will use smaller steps, giving the illusion that
the icons are moving slower.
IconSlideDelay will give the pause between steps, and is expressed in ms.
+5 -5
View File
@@ -190,7 +190,7 @@ Performance Tuning
==================
If you want to diminish Window Maker's memory usage and improve performance,
while keeping a nice appearance and good functionality, follow the items bellow:
while keeping a nice appearance and good functionality, follow the items below:
- use solid textures for everything, mainly title bars and menus. If you want a
nice looking desktop, use the Traditional style.
@@ -240,7 +240,7 @@ pointer faster;
clicked with the 0 (Ins) key.
The above key values work in a XFree86 3.2 (X11R6.1) X server but your
The above key values work on an XFree86 3.2 (X11R6.1) X server, but your
mileage may vary.
@@ -252,7 +252,7 @@ that crashes Window Maker, please send a backtrace with your bug report.
To make a useful backtrace, you need a core file with debugging information
produced by Window Maker when it crashes. It should have been installed without
stripping too.
being stripped.
To compile wmaker with debugging information:
@@ -287,7 +287,7 @@ The icons listed in COPYING.WTFPL and are distributed in this program and were
made by Banlu Kemiyatorn (]d), are licensed through the "do What The Fuck you
want to Public License". Read the COPYING.WTFPL file for the complete license.
NeXT, OpenStep and NEXTSTEP are a trademarks of NeXT Computer, Inc. All other
NeXT, OpenStep and NEXTSTEP are trademarks of NeXT Computer, Inc. All other
trademarks are property of their respective owners.
The authors reserve the right to make changes in the software without prior
@@ -307,7 +307,7 @@ screenshots.
The AUTHORS file contains a list of the people who have contributed to the
project. The name of people who have helped with localization (translation) can
project. The names of people who have helped with localization (translation) can
be found in po/README and Window Maker/README
If you have any comments, fixes and bug reports (filled BUGFORMs) send them
+31
View File
@@ -686,6 +686,37 @@ void W_BroadcastMessage(W_View *targetParent, XEvent *event);
void W_DispatchMessage(W_View *target, XEvent *event);
/* ---[ UTF-8 helpers ]--------------------------------------------------- */
static inline int oneUTF8CharBackward(const char *str, int len)
{
const unsigned char *ustr = (const unsigned char *)str;
int pos = 0;
while (len-- > 0 && ustr[--pos] >= 0x80 && ustr[pos] <= 0xbf) ;
return pos;
}
static inline int oneUTF8CharForward(const char *str, int len)
{
const unsigned char *ustr = (const unsigned char *)str;
int pos = 0;
while (len-- > 0 && ustr[++pos] >= 0x80 && ustr[pos] <= 0xbf) ;
return pos;
}
// find the beginning of the UTF8 char pointed by str
static inline int seekUTF8CharStart(const char *str, int len)
{
const unsigned char *ustr = (const unsigned char *)str;
int pos = 0;
while (len-- > 0 && ustr[pos] >= 0x80 && ustr[pos] <= 0xbf)
--pos;
return pos;
}
#ifdef __cplusplus
}
+7 -4
View File
@@ -122,11 +122,14 @@ static int fitText(const char *text, WMFont * font, int width, int wrap)
word1 = word2;
}
for (i = word1; i < word2; i++) {
w = WMWidthOfString(font, text, i);
if (w > width) {
/* Advance character by character (not byte by byte) */
i = word1;
while (i < word2) {
int next_i = i + oneUTF8CharForward(text + i, word2 - i);
w = WMWidthOfString(font, text, next_i);
if (w > width)
break;
}
i = next_i;
}
/* keep words complete if possible */
-29
View File
@@ -122,35 +122,6 @@ static WMSelectionProcs selectionHandler = {
#define TEXT_WIDTH2(tPtr, start, end) (WMWidthOfString((tPtr)->font, \
&((tPtr)->text[(start)]), (end) - (start)))
static inline int oneUTF8CharBackward(const char *str, int len)
{
const unsigned char *ustr = (const unsigned char *)str;
int pos = 0;
while (len-- > 0 && ustr[--pos] >= 0x80 && ustr[pos] <= 0xbf) ;
return pos;
}
static inline int oneUTF8CharForward(const char *str, int len)
{
const unsigned char *ustr = (const unsigned char *)str;
int pos = 0;
while (len-- > 0 && ustr[++pos] >= 0x80 && ustr[pos] <= 0xbf) ;
return pos;
}
// find the beginning of the UTF8 char pointed by str
static inline int seekUTF8CharStart(const char *str, int len)
{
const unsigned char *ustr = (const unsigned char *)str;
int pos = 0;
while (len-- > 0 && ustr[pos] >= 0x80 && ustr[pos] <= 0xbf)
--pos;
return pos;
}
static void normalizeRange(TextField * tPtr, WMRange * range)
{
if (range->position < 0 && range->count < 0)
+5
View File
@@ -132,6 +132,11 @@ static struct expert_option {
{ N_("Allow windows to take focus using mouse wheel."),
/* default: */ False, OPTION_WMAKER, "MouseWheelFocus"},
#ifdef USE_RANDR
{ N_("Automatically (de)activate monitors on hotplug events."),
/* default: */ False, OPTION_WMAKER, "HotplugMonitor"},
#endif
};
+12
View File
@@ -103,9 +103,21 @@ static struct keyOption {
{ "SelectKey", N_("Select active window") },
{ "FocusNextKey", N_("Focus next window") },
{ "FocusPrevKey", N_("Focus previous window") },
/* Directional window focus */
{ "FocusWindowLeftKey", N_("Focus the window to the left") },
{ "FocusWindowRightKey", N_("Focus the window to the right") },
{ "FocusWindowUpKey", N_("Focus the window above") },
{ "FocusWindowDownKey", N_("Focus the window below") },
{ "GroupNextKey", N_("Focus next group window") },
{ "GroupPrevKey", N_("Focus previous group window") },
/* Vim-like Window Marking */
{ "MarkSetKey", N_("Mark window: set mark") },
{ "MarkUnsetKey", N_("Mark window: unset mark") },
{ "MarkBringKey", N_("Mark window: bring marked window here") },
{ "MarkJumpKey", N_("Mark window: jump to marked window") },
{ "MarkSwapKey", N_("Mark window: swap with marked window") },
/* Workspace Related */
{ "WorkspaceMapKey", N_("Open workspace pager") },
{ "NextWorkspaceKey", N_("Switch to next workspace") },
+9
View File
@@ -23,6 +23,7 @@
#include "WPrefs.h"
#include <assert.h>
#include <X11/Xatom.h>
#ifdef HAVE_STDNORETURN
#include <stdnoreturn.h>
@@ -516,6 +517,9 @@ void Initialize(WMScreen * scr)
char **list;
int i;
char *path;
long pid;
Atom net_wm_pid;
Display *dpy = WMScreenDisplay(scr);
list = RSupportedFileFormats();
for (i = 0; list[i] != NULL; i++) {
@@ -547,6 +551,11 @@ void Initialize(WMScreen * scr)
WMRealizeWidget(WPrefs.win);
net_wm_pid = XInternAtom(dpy, "_NET_WM_PID", False);
pid = (long)getpid();
XChangeProperty(dpy, WMWidgetXID(WPrefs.win), net_wm_pid, XA_CARDINAL,
32, PropModeReplace, (unsigned char *)&pid, 1);
WMSetWindowMiniwindowImage(WPrefs.win, WMGetApplicationIconImage(scr));
WMMapWidget(WPrefs.win);
+2 -2
View File
@@ -657,11 +657,11 @@ dnl RandR support
dnl =============
m4_divert_push([INIT_PREPARE])dnl
AC_ARG_ENABLE([randr],
[AS_HELP_STRING([--enable-randr], [enable RandR extension support (NOT recommended, buggy)])],
[AS_HELP_STRING([--enable-randr], [enable RandR extension support for multi-monitor live reconfiguration])],
[AS_CASE(["$enableval"],
[yes|no], [],
[AC_MSG_ERROR([bad value $enableval for --enable-randr]) ]) ],
[enable_randr=no])
[enable_randr=auto])
m4_divert_pop([INIT_PREPARE])dnl
WM_XEXT_CHECK_XRANDR
+2
View File
@@ -27,6 +27,7 @@ POTFILES = \
$(top_srcdir)/src/framewin.c \
$(top_srcdir)/src/geomview.c \
$(top_srcdir)/src/icon.c \
$(top_srcdir)/src/keytree.c \
$(top_srcdir)/src/main.c \
$(top_srcdir)/src/menu.c \
$(top_srcdir)/src/misc.c \
@@ -40,6 +41,7 @@ POTFILES = \
$(top_srcdir)/src/pixmap.c \
$(top_srcdir)/src/placement.c \
$(top_srcdir)/src/properties.c \
$(top_srcdir)/src/randr.c \
$(top_srcdir)/src/resources.c \
$(top_srcdir)/src/rootmenu.c \
$(top_srcdir)/src/screen.c \
+3 -2
View File
@@ -61,6 +61,8 @@ wmaker_SOURCES = \
placement.h \
properties.c \
properties.h \
randr.c \
randr.h \
resources.c \
resources.h \
rootmenu.c \
@@ -195,6 +197,5 @@ defaults-callbacks-dynamic:
--source "$(srcdir)/defaults.c" --structure "optionList" \
--field-value-ptr 4 --field-callback 5 \
--struct-def "wPreferences=$(srcdir)/WindowMaker.h" \
--struct-def "legacy_minipreview_config=$(srcdir)/defaults.c" \
--callback "getBool=char, getEnum=char, getInt=int" \
--callback "getBool=char, getEnum=char, getInt=int, getString=char*" \
--callback "getPathList=char*, getCoord=WCoord"
+4
View File
@@ -410,6 +410,9 @@ extern struct WPreferences {
char dont_blink; /* do not blink icon selection */
char keep_dock_on_primary_head; /* keep dock on primary head */
#ifdef USE_RANDR
char hotplug_monitor; /* auto-(de)activate monitors */
#endif
/* Appearance options */
char new_style; /* Use newstyle buttons */
@@ -606,6 +609,7 @@ extern struct wmaker_global_variables {
Atom icon_size;
Atom icon_tile;
Atom mark_key;
} wmaker;
} atom;
+101 -1
View File
@@ -4,7 +4,7 @@
*
* Copyright (c) 1997-2003 Alfredo K. Kojima
* Copyright (c) 1998-2003 Dan Pascu
* Copyright (c) 2014-2023 Window Maker Team
* Copyright (c) 2014-2026 Window Maker Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -240,6 +240,100 @@ void wSetFocusTo(WScreen *scr, WWindow *wwin)
old_scr = scr;
}
/*
*----------------------------------------------------------------------
* wSetFocusToDirection--
* Moves focus to the nearest window in the given cardinal
* direction (DIRECTION_LEFT, DIRECTION_RIGHT, DIRECTION_UP,
* DIRECTION_DOWN from xinerama.h).
*
* Selection algorithm: candidate windows are scored by
* (primary_distance + perpendicular_offset). A large penalty is
* added when the perpendicular offset exceeds the primary distance
* (i.e. the candidate is more than 45 degrees off-axis). The window
* with the lowest score wins. If no candidate exists in the requested
* direction the call is silently ignored.
*----------------------------------------------------------------------
*/
void wSetFocusToDirection(WScreen *scr, int direction)
{
WWindow *focused = scr->focused_window;
WWindow *best = NULL;
WWindow *candidate = NULL;
int my_cx, my_cy;
long best_score = -1;
if (!focused || !focused->flags.mapped)
return;
/* centre of the focused window */
my_cx = focused->frame_x + (int)focused->frame->core->width / 2;
my_cy = focused->frame_y + (int)focused->frame->core->height / 2;
/* Iterate from most-recently-focused to least-recently-focused */
for (candidate = focused->prev; candidate != NULL; candidate = candidate->prev) {
int his_cx, his_cy, distance, offset;
long score;
if (!candidate->flags.mapped)
continue;
if (WFLAGP(candidate, no_focusable))
continue;
if (!candidate->frame || candidate->frame->workspace != scr->current_workspace)
continue;
/* ignore fully covered windows if cannot raised them */
if (!wPreferences.circ_raise && wWindowIsFullyCovered(candidate))
continue;
/* relative centre of candidate */
his_cx = (candidate->frame_x - my_cx) + (int)candidate->frame->core->width / 2;
his_cy = (candidate->frame_y - my_cy) + (int)candidate->frame->core->height / 2;
switch (direction) {
case DIRECTION_RIGHT:
distance = his_cx;
offset = his_cy < 0 ? -his_cy : his_cy;
break;
case DIRECTION_LEFT:
distance = -his_cx;
offset = his_cy < 0 ? -his_cy : his_cy;
break;
case DIRECTION_DOWN:
distance = his_cy;
offset = his_cx < 0 ? -his_cx : his_cx;
break;
case DIRECTION_UP:
distance = -his_cy;
offset = his_cx < 0 ? -his_cx : his_cx;
break;
default:
continue;
}
/* candidate must be strictly in the requested direction */
if (distance <= 0)
continue;
score = distance + offset;
/* heavy penalty for windows more than 45 degrees off-axis */
if (offset > distance)
score += 1000000L;
if (best_score < 0 || score < best_score) {
best = candidate;
best_score = score;
}
}
if (best) {
if (wPreferences.circ_raise)
wRaiseFrame(best->frame->core);
wSetFocusTo(scr, best);
}
}
void wShadeWindow(WWindow *wwin)
{
@@ -1608,6 +1702,9 @@ void wDeiconifyWindow(WWindow *wwin)
}
}
/* Relocate to an active head if the stored position is in dead space */
wWindowSnapToHead(wwin);
/* if the window is in another workspace, do it silently */
if (!netwm_hidden) {
#ifdef USE_ANIMATIONS
@@ -1876,6 +1973,9 @@ static void unhideWindow(WIcon *icon, int icon_x, int icon_y, WWindow *wwin, int
wwin->flags.hidden = 0;
/* Relocate to an active head if the stored position is in dead space */
wWindowSnapToHead(wwin);
#ifdef USE_ANIMATIONS
if (!wwin->screen_ptr->flags.startup && !wPreferences.no_animations && animate) {
animateResize(wwin->screen_ptr, icon_x, icon_y,
+1
View File
@@ -41,6 +41,7 @@
#define SAVE_GEOMETRY_ALL SAVE_GEOMETRY_X | SAVE_GEOMETRY_Y | SAVE_GEOMETRY_WIDTH | SAVE_GEOMETRY_HEIGHT
void wSetFocusTo(WScreen *scr, WWindow *wwin);
void wSetFocusToDirection(WScreen *scr, int direction);
int wMouseMoveWindow(WWindow *wwin, XEvent *ev);
int wKeyboardMoveResizeWindow(WWindow *wwin);
+4
View File
@@ -146,6 +146,10 @@ WAppIcon *wAppIconCreateForDock(WScreen *scr, const char *command, const char *w
void create_appicon_for_application(WApplication *wapp, WWindow *wwin)
{
/* Transient windows should never get their own appicon */
if (wwin->transient_for != None && wwin->transient_for != wwin->screen_ptr->root_win)
return;
/* Try to create an icon from the dock or clip */
create_appicon_from_dock(wwin, wapp, wapp->main_window);
+24 -1
View File
@@ -5,7 +5,6 @@
* Copyright (c) 1997-2003 Alfredo K. Kojima
* Copyright (c) 1998-2003 Dan Pascu
* Copyright (c) 2014-2026 Window Maker Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -527,6 +526,10 @@ WDefaultEntry optionList[] = {
{"KeepDockOnPrimaryHead", "NO", NULL,
&wPreferences.keep_dock_on_primary_head, getBool, updateDock,
NULL, NULL},
#ifdef USE_RANDR
{"HotplugMonitor", "NO", NULL,
&wPreferences.hotplug_monitor, getBool, NULL, NULL, NULL},
#endif
{"HotCorners", "NO", NULL,
&wPreferences.hot_corners, getBool, NULL, NULL, NULL},
{"HotCornerDelay", "250", (void *)&wPreferences.hot_corner_delay,
@@ -718,6 +721,15 @@ WDefaultEntry optionList[] = {
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"FocusPrevKey", "Mod1+Shift+Tab", (void *)WKBD_FOCUSPREV,
NULL, getKeybind, setKeyGrab, NULL, NULL},
/* Directional window focus */
{"FocusWindowLeftKey", "None", (void *)WKBD_FOCUSLEFT,
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"FocusWindowRightKey", "None", (void *)WKBD_FOCUSRIGHT,
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"FocusWindowUpKey", "None", (void *)WKBD_FOCUSUP,
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"FocusWindowDownKey", "None", (void *)WKBD_FOCUSDOWN,
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"GroupNextKey", "None", (void *)WKBD_GROUPNEXT,
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"GroupPrevKey", "None", (void *)WKBD_GROUPPREV,
@@ -820,6 +832,17 @@ WDefaultEntry optionList[] = {
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"PartialCaptureKey", "None", (void *)WKBD_PRINTP,
NULL, getKeybind, setKeyGrab, NULL, NULL},
/* Vim-like Window Marking */
{"MarkSetKey", "None", (void *)WKBD_MARK_SET,
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"MarkUnsetKey", "None", (void *)WKBD_MARK_UNSET,
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"MarkBringKey", "None", (void *)WKBD_MARK_BRING,
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"MarkJumpKey", "None", (void *)WKBD_MARK_JUMP,
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"MarkSwapKey", "None", (void *)WKBD_MARK_SWAP,
NULL, getKeybind, setKeyGrab, NULL, NULL},
#ifdef KEEP_XKB_LOCK_STATUS
{"ToggleKbdModeKey", "None", (void *)WKBD_TOGGLE,
+37
View File
@@ -3054,6 +3054,43 @@ void wDockSwap(WDock *dock)
wScreenUpdateUsableArea(scr);
}
/* Snap a clip back onto a visible head after a RandR reconfiguration,
* preserving its relative position within that head */
void wClipSnapToHead(WDock *clip)
{
WScreen *scr = clip->screen_ptr;
WMRect rect, head;
float rel_x, rel_y;
int x = clip->x_pos;
int y = clip->y_pos;
/* Already fully on a visible head, nothing to do */
if (!wScreenKeepInside(scr, &x, &y, ICON_SIZE, ICON_SIZE))
return;
rect.pos.x = clip->x_pos;
rect.pos.y = clip->y_pos;
rect.size.width = ICON_SIZE;
rect.size.height = ICON_SIZE;
/* Find the nearest remaining head to the clip's old position */
head = wGetRectForHead(scr, wGetHeadForRect(scr, rect));
/* Compute fractional position within that head and clamp to [0..1] */
rel_x = (float)(clip->x_pos - head.pos.x) / (float)head.size.width;
rel_y = (float)(clip->y_pos - head.pos.y) / (float)head.size.height;
if (rel_x < 0.0f) rel_x = 0.0f;
else if (rel_x > 1.0f) rel_x = 1.0f;
if (rel_y < 0.0f) rel_y = 0.0f;
else if (rel_y > 1.0f) rel_y = 1.0f;
x = head.pos.x + (int)(rel_x * (head.size.width - ICON_SIZE));
y = head.pos.y + (int)(rel_y * (head.size.height - ICON_SIZE));
moveDock(clip, x, y);
}
static pid_t execCommand(WAppIcon *btn, const char *command, WSavedState *state)
{
WScreen *scr = btn->icon->core->screen_ptr;
+1
View File
@@ -92,6 +92,7 @@ WAppIcon *wDockFindIconForWindow(WDock *dock, Window window);
void wDockDoAutoLaunch(WDock *dock, int workspace);
void wDockLaunchWithState(WAppIcon *btn, WSavedState *state);
void wDockSwap(WDock *dock);
void wClipSnapToHead(WDock *clip);
#ifdef USE_DOCK_XDND
int wDockReceiveDNDDrop(WScreen *scr, XEvent *event);
+167 -14
View File
@@ -3,7 +3,7 @@
* Window Maker window manager
*
* Copyright (c) 1997-2003 Alfredo K. Kojima
* Copyright (c) 2014-2023 Window Maker Team
* Copyright (c) 2014-2026 Window Maker Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -46,7 +46,7 @@
#endif
#ifdef USE_RANDR
#include <X11/extensions/Xrandr.h>
#include "randr.h"
#endif
#include <X11/XKBlib.h>
@@ -271,10 +271,6 @@ void DispatchEvent(XEvent * event)
break;
case ConfigureNotify:
#ifdef USE_RANDR
if (event->xconfigure.window == DefaultRootWindow(dpy))
XRRUpdateConfiguration(event);
#endif
break;
case SelectionRequest:
@@ -618,14 +614,14 @@ static void handleExtensions(XEvent * event)
#endif /*KEEP_XKB_LOCK_STATUS */
}
#ifdef USE_RANDR
if (w_global.xext.randr.supported && event->type == (w_global.xext.randr.event_base + RRScreenChangeNotify)) {
/* From xrandr man page: "Clients must call back into Xlib using
* XRRUpdateConfiguration when screen configuration change notify
* events are generated */
XRRUpdateConfiguration(event);
WCHANGE_STATE(WSTATE_RESTARTING);
Shutdown(WSRestartPreparationMode);
Restart(NULL,True);
if (w_global.xext.randr.supported &&
(event->type == (w_global.xext.randr.event_base + RRScreenChangeNotify) ||
event->type == (w_global.xext.randr.event_base + RRNotify))) {
WScreen *randr_scr;
randr_scr = wScreenForRootWindow(event->xany.window);
if (randr_scr)
wRandRHandleNotify(randr_scr, event);
}
#endif
}
@@ -1475,6 +1471,12 @@ static void wCancelChainTimer(void)
#define ISMAPPED(w) ((w) && !(w)->flags.miniaturized && ((w)->flags.mapped || (w)->flags.shaded))
#define ISFOCUSED(w) ((w) && (w)->flags.focused)
static void startMarkCapture(WScreen *scr, Display *dpy, WMarkCaptureMode mode)
{
scr->flags.mark_capture_mode = mode;
XGrabKeyboard(dpy, scr->root_win, False, GrabModeAsync, GrabModeAsync, CurrentTime);
}
static void dispatchWKBDCommand(int command, WScreen *scr, WWindow *wwin, XEvent *event)
{
short widx;
@@ -1730,6 +1732,22 @@ static void dispatchWKBDCommand(int command, WScreen *scr, WWindow *wwin, XEvent
StartWindozeCycle(wwin, event, False, False);
break;
case WKBD_FOCUSLEFT:
wSetFocusToDirection(scr, DIRECTION_LEFT);
break;
case WKBD_FOCUSRIGHT:
wSetFocusToDirection(scr, DIRECTION_RIGHT);
break;
case WKBD_FOCUSUP:
wSetFocusToDirection(scr, DIRECTION_UP);
break;
case WKBD_FOCUSDOWN:
wSetFocusToDirection(scr, DIRECTION_DOWN);
break;
case WKBD_GROUPNEXT:
StartWindozeCycle(wwin, event, True, True);
break;
@@ -1979,6 +1997,29 @@ static void dispatchWKBDCommand(int command, WScreen *scr, WWindow *wwin, XEvent
}
break;
#endif /* KEEP_XKB_LOCK_STATUS */
case WKBD_MARK_SET:
if (ISMAPPED(wwin) && ISFOCUSED(wwin))
startMarkCapture(scr, dpy, MARK_CAPTURE_SET);
break;
case WKBD_MARK_UNSET:
if (ISMAPPED(wwin) && ISFOCUSED(wwin) && wwin->mark_key_label != NULL)
wWindowUnsetMark(wwin);
break;
case WKBD_MARK_BRING:
startMarkCapture(scr, dpy, MARK_CAPTURE_BRING);
break;
case WKBD_MARK_JUMP:
startMarkCapture(scr, dpy, MARK_CAPTURE_JUMP);
break;
case WKBD_MARK_SWAP:
if (ISMAPPED(wwin) && ISFOCUSED(wwin))
startMarkCapture(scr, dpy, MARK_CAPTURE_SWAP);
break;
}
}
@@ -1994,6 +2035,118 @@ static void handleKeyPress(XEvent * event)
/* ignore CapsLock */
modifiers = event->xkey.state & w_global.shortcut.modifiers_mask;
/* ----------------------------------------------------------- *
* Window mark capture mode *
* *
* We grabbed the keyboard so all keypresses come here until *
* we release the grab. *
* ------------------------------------------------------------ */
if (scr->flags.mark_capture_mode != MARK_CAPTURE_IDLE) {
int capture_mode = scr->flags.mark_capture_mode;
KeySym cap_ksym;
char label[MAX_SHORTCUT_LENGTH];
/* Skip modifier-only keypresses */
cap_ksym = XLookupKeysym(&event->xkey, 0);
if (cap_ksym == NoSymbol || IsModifierKey(cap_ksym))
return;
/* Real key received: exit capture mode and release grab */
scr->flags.mark_capture_mode = MARK_CAPTURE_IDLE;
XUngrabKeyboard(dpy, CurrentTime);
if (!GetCanonicalShortcutLabel(modifiers, cap_ksym, label, sizeof(label)))
wstrlcpy(label, "?", sizeof(label));
if (capture_mode == MARK_CAPTURE_SET) {
WWindow *target = scr->focused_window;
Bool conflict = False;
int i;
/* Conflict check against static wmaker bindings */
for (i = 0; i < WKBD_LAST; i++) {
if (wKeyBindings[i].keycode == event->xkey.keycode && wKeyBindings[i].modifier == modifiers) {
wwarning("window mark: '%s' is already a wmaker binding, mark not assigned", label);
conflict = True;
break;
}
}
/* Conflict check against existing marks on other windows */
if (!conflict) {
WWindow *tw = scr->focused_window;
while (tw) {
if (tw != target && tw->mark_key_label != NULL && strcmp(tw->mark_key_label, label) == 0) {
wwarning("window mark: label '%s' is already used by another window, mark not assigned", label);
conflict = True;
break;
}
tw = tw->prev;
}
}
if (!conflict && target != NULL)
wWindowSetMark(target, label);
} else {
/* Find marked window by label */
WWindow *tw = scr->focused_window;
while (tw) {
if (tw->mark_key_label != NULL && strcmp(tw->mark_key_label, label) == 0)
break;
tw = tw->prev;
}
if (tw == NULL) {
wwarning("window mark: no window labelled '%s'", label);
} else if (capture_mode == MARK_CAPTURE_BRING) {
if (tw->frame->workspace != scr->current_workspace)
wWindowChangeWorkspace(tw, scr->current_workspace);
wMakeWindowVisible(tw);
} else if (capture_mode == MARK_CAPTURE_JUMP) {
wMakeWindowVisible(tw);
} else {
/* MARK_CAPTURE_SWAP: swap position, size and workspace between focused and tw */
WWindow *focused = scr->focused_window;
int fx, fy, fw, fh, tx, ty, tw_w, tw_h;
int f_ws, t_ws;
if (focused == NULL || focused == tw)
return;
/* Snapshot both geometries */
fx = focused->frame_x;
fy = focused->frame_y;
fw = focused->client.width;
fh = focused->client.height;
f_ws = focused->frame->workspace;
tx = tw->frame_x;
ty = tw->frame_y;
tw_w = tw->client.width;
tw_h = tw->client.height;
t_ws = tw->frame->workspace;
/* Swap workspaces first so configure lands in the right one */
if (f_ws != t_ws) {
wWindowChangeWorkspace(focused, t_ws);
wWindowChangeWorkspace(tw, f_ws);
}
/* Swap positions and sizes */
wWindowConfigure(focused, tx, ty, tw_w, tw_h);
wWindowConfigure(tw, fx, fy, fw, fh);
/* Follow origin window: switch view to the workspace it landed in,
* then restore focus to it */
if (f_ws != t_ws)
wWorkspaceChange(scr, t_ws);
wSetFocusTo(scr, focused);
}
}
return;
}
/* ------------------------------------------------------------------ *
* Trie-based key-chain matching *
* *
+3
View File
@@ -803,6 +803,9 @@ RImage *get_rimage_icon_from_wm_hints(WIcon *icon)
wwin = icon->owner;
if (!wwin->wm_hints)
return NULL;
if (!getSize(wwin->wm_hints->icon_pixmap, &w, &h, &d)) {
icon->owner->wm_hints->flags &= ~IconPixmapHint;
return NULL;
+9
View File
@@ -77,9 +77,18 @@ enum {
WKBD_WORKSPACEMAP,
WKBD_FOCUSNEXT,
WKBD_FOCUSPREV,
WKBD_FOCUSLEFT,
WKBD_FOCUSRIGHT,
WKBD_FOCUSUP,
WKBD_FOCUSDOWN,
WKBD_GROUPNEXT,
WKBD_GROUPPREV,
WKBD_CENTRAL,
WKBD_MARK_SET,
WKBD_MARK_UNSET,
WKBD_MARK_BRING,
WKBD_MARK_JUMP,
WKBD_MARK_SWAP,
/* window, menu */
WKBD_CLOSE,
+35 -43
View File
@@ -847,6 +847,34 @@ static char *keysymToString(KeySym keysym, unsigned int state)
}
#endif
Bool GetCanonicalShortcutLabel(unsigned int modifiers, KeySym ksym, char *buf, size_t bufsz)
{
static const struct { unsigned int mask; const char *name; } mt[] = {
{ ShiftMask, "Shift+" },
{ ControlMask, "Control+" },
{ Mod1Mask, "Mod1+" },
{ Mod2Mask, "Mod2+" },
{ Mod3Mask, "Mod3+" },
{ Mod4Mask, "Mod4+" },
{ Mod5Mask, "Mod5+" },
{ 0, NULL }
};
const char *kname = XKeysymToString(ksym);
size_t i;
if (!kname)
return False;
buf[0] = '\0';
for (i = 0; mt[i].mask; i++) {
if (modifiers & mt[i].mask)
wstrlcat(buf, mt[i].name, bufsz);
}
wstrlcat(buf, kname, bufsz);
return True;
}
char *GetShortcutString(const char *shortcut)
{
char *buffer = NULL;
@@ -882,50 +910,12 @@ char *GetShortcutString(const char *shortcut)
char *GetShortcutKey(WShortKey key)
{
char buf[MAX_SHORTCUT_LENGTH];
char *wr, *result, *seg;
char *result, *seg;
int step;
void append_string(const char *text)
{
const char *s = text;
while (*s) {
if (wr >= buf + sizeof(buf) - 1)
break;
*wr++ = *s++;
}
}
void append_modifier(int modifier_index, const char *fallback_name)
{
if (wPreferences.modifier_labels[modifier_index])
append_string(wPreferences.modifier_labels[modifier_index]);
else
append_string(fallback_name);
}
Bool build_token(unsigned int mod, KeyCode kcode)
{
const char *kname = XKeysymToString(W_KeycodeToKeysym(dpy, kcode, 0));
if (!kname)
return False;
wr = buf;
if (mod & ControlMask) append_modifier(1, "Control+");
if (mod & ShiftMask) append_modifier(0, "Shift+");
if (mod & Mod1Mask) append_modifier(2, "Mod1+");
if (mod & Mod2Mask) append_modifier(3, "Mod2+");
if (mod & Mod3Mask) append_modifier(4, "Mod3+");
if (mod & Mod4Mask) append_modifier(5, "Mod4+");
if (mod & Mod5Mask) append_modifier(6, "Mod5+");
append_string(kname);
*wr = '\0';
return True;
}
if (!build_token(key.modifier, key.keycode))
if (!GetCanonicalShortcutLabel(key.modifier,
W_KeycodeToKeysym(dpy, key.keycode, 0),
buf, sizeof(buf)))
return NULL;
/* Convert the leader token to its display string */
@@ -938,7 +928,9 @@ char *GetShortcutKey(WShortKey key)
if (key.chain_keycodes[step] == 0)
break;
if (!build_token(key.chain_modifiers[step], key.chain_keycodes[step]))
if (!GetCanonicalShortcutLabel(key.chain_modifiers[step],
W_KeycodeToKeysym(dpy, key.chain_keycodes[step], 0),
buf, sizeof(buf)))
break;
seg = GetShortcutString(buf);
+1
View File
@@ -50,6 +50,7 @@ char *ExpandOptions(WScreen * scr, const char *cmdline);
void ExecuteInputCommand(WScreen *scr, const char *cmdline);
void ExecuteExitCommand(WScreen *scr, long quickmode);
char *GetShortcutString(const char *text);
Bool GetCanonicalShortcutLabel(unsigned int modifiers, KeySym ksym, char *buf, size_t bufsz);
char *GetShortcutKey(WShortKey key);
char *EscapeWM_CLASS(const char *name, const char *class);
char *StrConcatDot(const char *a, const char *b);
+9 -9
View File
@@ -457,11 +457,10 @@ static void drawTransparentFrame(WWindow * wwin, int x, int y, int width, int he
GC gc = wwin->screen_ptr->frame_gc;
int h = 0;
int bottom = 0;
int fb = 0;
if (HAS_BORDER_WITH_SELECT(wwin)) {
x += wwin->screen_ptr->frame_border_width;
y += wwin->screen_ptr->frame_border_width;
}
if (HAS_BORDER_WITH_SELECT(wwin))
fb = wwin->screen_ptr->frame_border_width;
if (HAS_TITLEBAR(wwin) && !wwin->flags.shaded) {
h = WMFontHeight(wwin->screen_ptr->title_font) + (wPreferences.window_title_clearance +
@@ -478,13 +477,13 @@ static void drawTransparentFrame(WWindow * wwin, int x, int y, int width, int he
(e.g. interactive placement), frame does not point to anything. */
bottom = RESIZEBAR_HEIGHT;
}
XDrawRectangle(dpy, root, gc, x - 1, y - 1, width + 1, height + 1);
XDrawRectangle(dpy, root, gc, x, y, width - 1 + 2 * fb, height - 1 + 2 * fb);
if (h > 0) {
XDrawLine(dpy, root, gc, x, y + h - 1, x + width, y + h - 1);
XDrawLine(dpy, root, gc, x, y + fb + h - 1, x + 2 * fb + width, y + fb + h - 1);
}
if (bottom > 0) {
XDrawLine(dpy, root, gc, x, y + height - bottom, x + width, y + height - bottom);
XDrawLine(dpy, root, gc, x, y + fb + height - bottom, x + 2 * fb + width, y + fb + height - bottom);
}
}
@@ -1200,7 +1199,7 @@ updateWindowPosition(WWindow * wwin, MoveData * data, Bool doResistance,
static void draw_snap_frame(WWindow *wwin, int direction)
{
WScreen *scr;
int head, x, y;
int head, x, y, fb;
unsigned int width, height;
WMRect rect;
@@ -1212,6 +1211,7 @@ static void draw_snap_frame(WWindow *wwin, int direction)
y = rect.pos.y;
width = rect.size.width;
height = rect.size.height;
fb = HAS_BORDER_WITH_SELECT(wwin) ? 2 * wwin->screen_ptr->frame_border_width : 0;
switch (direction) {
case SNAP_LEFT:
@@ -1258,7 +1258,7 @@ static void draw_snap_frame(WWindow *wwin, int direction)
break;
}
drawTransparentFrame(wwin, x, y, width, height);
drawTransparentFrame(wwin, x, y, width - fb, height - fb);
}
static int get_snap_direction(WScreen *scr, int x, int y)
+629
View File
@@ -0,0 +1,629 @@
/* randr.c - RandR multi-monitor support
*
* Window Maker window manager
*
* Copyright (c) 2026 Window Maker Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "wconfig.h"
#ifdef USE_RANDR
#include <string.h>
#include <X11/Xlib.h>
#include "randr.h"
#include "window.h"
#include "framewin.h"
#include "xinerama.h"
#include "wmspec.h"
#include "dock.h"
#include "workspace.h"
#include "actions.h"
#define RANDR_MAX_OUTPUTS 32
#define RANDR_DEBOUNCE_DELAY 100 /* milliseconds */
typedef struct {
RROutput xid;
RRCrtc crtc;
int x, y, w, h;
Rotation rotation; /* current CRTC rotation */
Bool connected; /* RR_Connected */
Bool stale; /* mark-scan scratch flag */
char name[64];
} WRandROutput;
typedef struct {
WRandROutput outputs[RANDR_MAX_OUTPUTS];
int n_outputs;
} WRandRState;
/* Initial output scan to populate state from scratch */
static void wRandR_Scan(WScreen *scr, WRandRState *state)
{
XRRScreenResources *sr;
int i;
state->n_outputs = 0;
sr = XRRGetScreenResourcesCurrent(dpy, scr->root_win);
if (!sr)
return;
for (i = 0; i < sr->noutput && state->n_outputs < RANDR_MAX_OUTPUTS; i++) {
XRROutputInfo *oi;
WRandROutput *o;
oi = XRRGetOutputInfo(dpy, sr, sr->outputs[i]);
if (!oi)
continue;
o = &state->outputs[state->n_outputs++];
memset(o, 0, sizeof(*o));
o->xid = sr->outputs[i];
o->connected = (oi->connection == RR_Connected);
o->crtc = oi->crtc;
strncpy(o->name, oi->name, sizeof(o->name) - 1);
if (oi->crtc != None) {
XRRCrtcInfo *ci = XRRGetCrtcInfo(dpy, sr, oi->crtc);
if (ci) {
o->x = ci->x;
o->y = ci->y;
o->w = ci->width;
o->h = ci->height;
o->rotation = ci->rotation;
XRRFreeCrtcInfo(ci);
}
}
XRRFreeOutputInfo(oi);
}
XRRFreeScreenResources(sr);
}
/* Primary head detection */
static int wRandR_PrimaryHeadIndex(Window root, const RROutput *xids, int count)
{
RROutput primary_xid = XRRGetOutputPrimary(dpy, root);
int i;
if (primary_xid != None) {
for (i = 0; i < count; i++) {
if (xids[i] == primary_xid)
return i;
}
}
return 0;
}
/* Push RandR geometry into the Xinerama head layer */
static void wRandR_ApplyToXinerama(WScreen *scr, WRandRState *state)
{
WMRect new_screens[RANDR_MAX_OUTPUTS];
RROutput new_xids[RANDR_MAX_OUTPUTS]; /* parallel: XID of each head */
int count = 0;
int old_count;
int i;
for (i = 0; i < state->n_outputs && count < RANDR_MAX_OUTPUTS; i++) {
WRandROutput *o = &state->outputs[i];
int j, dup = 0;
/* Collect outputs that are truly active */
if (o->crtc == None || o->w == 0 || o->h == 0)
continue;
/* Deduplicate mirrored outputs that share the same CRTC origin */
for (j = 0; j < count; j++) {
if (new_screens[j].pos.x == o->x && new_screens[j].pos.y == o->y) {
dup = 1;
break;
}
}
if (dup)
continue;
new_screens[count].pos.x = o->x;
new_screens[count].pos.y = o->y;
new_screens[count].size.width = o->w;
new_screens[count].size.height = o->h;
new_xids[count] = o->xid;
count++;
}
/* Fallback: if every output disappeared, use the full virtual screen so
* we never end up with zero heads */
if (count == 0) {
new_screens[0].pos.x = 0;
new_screens[0].pos.y = 0;
new_screens[0].size.width = scr->scr_width;
new_screens[0].size.height = scr->scr_height;
new_xids[0] = None;
count = 1;
}
old_count = wXineramaHeads(scr);
/* Replace the screens array */
if (scr->xine_info.screens)
wfree(scr->xine_info.screens);
scr->xine_info.screens = wmalloc(sizeof(WMRect) * (count + 1));
memcpy(scr->xine_info.screens, new_screens, sizeof(WMRect) * count);
scr->xine_info.count = count;
scr->xine_info.primary_head = wRandR_PrimaryHeadIndex(scr->root_win, new_xids, count);
/* Refresh cached screen dimensions (updated by XRRUpdateConfiguration) */
scr->scr_width = WidthOfScreen(ScreenOfDisplay(dpy, scr->screen));
scr->scr_height = HeightOfScreen(ScreenOfDisplay(dpy, scr->screen));
/* Reallocate per-head usable area arrays if the head count changed */
if (old_count != count) {
wfree(scr->usableArea);
wfree(scr->totalUsableArea);
scr->usableArea = wmalloc(sizeof(WArea) * count);
scr->totalUsableArea = wmalloc(sizeof(WArea) * count);
}
/* Seed usable areas from new geometry */
for (i = 0; i < count; i++) {
WMRect *r = &scr->xine_info.screens[i];
scr->usableArea[i].x1 = scr->totalUsableArea[i].x1 = r->pos.x;
scr->usableArea[i].y1 = scr->totalUsableArea[i].y1 = r->pos.y;
scr->usableArea[i].x2 = scr->totalUsableArea[i].x2 = r->pos.x + r->size.width;
scr->usableArea[i].y2 = scr->totalUsableArea[i].y2 = r->pos.y + r->size.height;
}
}
/* Bring any off-screen windows in */
static void wRandR_BringAllWindowsInside(WScreen *scr)
{
WWindow *wwin;
int i;
for (wwin = scr->focused_window; wwin != NULL; wwin = wwin->prev) {
int bw, wx, wy, ww, wh;
int fully_inside = 0;
if (!wwin->flags.mapped || wwin->flags.hidden)
continue;
bw = HAS_BORDER(wwin) ? scr->frame_border_width : 0;
wx = wwin->frame_x - bw;
wy = wwin->frame_y - bw;
ww = (int)wwin->frame->core->width + 2 * bw;
wh = (int)wwin->frame->core->height + 2 * bw;
/* Skip windows already fully contained within a surviving head */
for (i = 0; i < wXineramaHeads(scr); i++) {
WMRect r = wGetRectForHead(scr, i);
if (wx >= r.pos.x && wy >= r.pos.y &&
wx + ww <= r.pos.x + (int)r.size.width &&
wy + wh <= r.pos.y + (int)r.size.height) {
fully_inside = 1;
break;
}
}
if (!fully_inside)
wWindowSnapToHead(wwin);
}
}
/* Auto-deactivate a physically disconnected output that still holds a CRTC
*
* When a cable is pulled or monitor turned off, the X server sets
* connection=RR_Disconnected but does NOT free the CRTC automatically.
* We must call XRRSetCrtcConfig with mode=None to release it,
* which causes the server to fire a fresh batch of RandR events.
*
* Returns True if at least one CRTC was freed */
static Bool wRandR_AutoDeactivate(WScreen *scr, WRandRState *state, XRRScreenResources *sr)
{
Bool deactivated = False;
int i;
for (i = 0; i < state->n_outputs; i++) {
WRandROutput *o = &state->outputs[i];
Status ret;
if (o->connected || o->crtc == None || o->stale)
continue;
/* Release the CRTC: no mode, no outputs */
ret = XRRSetCrtcConfig(dpy, sr, o->crtc, sr->timestamp,
0, 0, None, RR_Rotate_0, NULL, 0);
if (ret == RRSetConfigSuccess) {
wwarning("RandR: released CRTC for disconnected output %s", o->name);
o->crtc = None;
o->w = o->h = 0;
deactivated = True;
} else {
wwarning("RandR: failed to release CRTC for output %s", o->name);
}
}
/* Compact and shrink the virtual framebuffer to the bounding box of remaining active outputs */
if (deactivated) {
int min_x = scr->scr_width;
int max_x = 0, max_y = 0;
int j;
for (i = 0; i < state->n_outputs; i++) {
WRandROutput *a = &state->outputs[i];
if (a->crtc == None || a->w == 0 || a->h == 0)
continue;
if (a->x < min_x) min_x = a->x;
if (a->x + a->w > max_x) max_x = a->x + a->w;
if (a->y + a->h > max_y) max_y = a->y + a->h;
}
/* Slide surviving outputs left to eliminate dead space at the origin */
if (min_x > 0 && max_x > 0) {
for (i = 0; i < state->n_outputs; i++) {
WRandROutput *a = &state->outputs[i];
XRRCrtcInfo *ci;
if (a->crtc == None || a->w == 0 || a->h == 0)
continue;
ci = XRRGetCrtcInfo(dpy, sr, a->crtc);
if (!ci)
continue;
XRRSetCrtcConfig(dpy, sr, a->crtc, sr->timestamp,
a->x - min_x, a->y,
ci->mode, ci->rotation,
ci->outputs, ci->noutput);
for (j = 0; j < state->n_outputs; j++) {
if (state->outputs[j].crtc == a->crtc)
state->outputs[j].x -= min_x;
}
XRRFreeCrtcInfo(ci);
}
max_x -= min_x;
}
/* Shrink the virtual framebuffer down to the compacted bounding box */
if (max_x > 0 && max_y > 0 &&
(max_x < scr->scr_width || max_y < scr->scr_height)) {
int mm_w = (int)((long)WidthMMOfScreen(ScreenOfDisplay(dpy, scr->screen))
* max_x / scr->scr_width);
int mm_h = (int)((long)HeightMMOfScreen(ScreenOfDisplay(dpy, scr->screen))
* max_y / scr->scr_height);
XRRSetScreenSize(dpy, scr->root_win, max_x, max_y, mm_w, mm_h);
}
}
return deactivated;
}
/* Auto-activate a newly connected output that has no CRTC assigned yet
*
* Mirrors "xrandr --auto": find a free CRTC, pick the preferred mode,
* place the new head to the right of all active outputs,
* expand the virtual framebuffer if needed, then call XRRSetCrtcConfig.
*
* Returns True if at least one output was activated */
static Bool wRandR_AutoActivate(WScreen *scr, WRandRState *state, XRRScreenResources *sr)
{
Bool activated = False;
int i, j;
for (i = 0; i < state->n_outputs; i++) {
WRandROutput *o = &state->outputs[i];
XRROutputInfo *oi;
RRCrtc free_crtc = None;
Rotation rotation = RR_Rotate_0;
RRMode best_mode = None;
int mode_w = 0, mode_h = 0;
int new_x = 0;
int new_vw, new_vh;
Status ret;
/* Only process newly connected outputs that have no CRTC yet */
if (!o->connected || o->crtc != None)
continue;
oi = XRRGetOutputInfo(dpy, sr, o->xid);
if (!oi)
continue;
if (oi->ncrtc == 0 || oi->nmode == 0) {
XRRFreeOutputInfo(oi);
continue;
}
/* Find a free CRTC that is idle and capable of driving this output */
for (j = 0; j < oi->ncrtc; j++) {
XRRCrtcInfo *ci = XRRGetCrtcInfo(dpy, sr, oi->crtcs[j]);
if (ci) {
if (ci->noutput == 0) {
int k;
for (k = 0; k < (int)ci->npossible; k++) {
if (ci->possible[k] == o->xid) {
free_crtc = oi->crtcs[j];
if (ci->rotations & RR_Rotate_0)
rotation = RR_Rotate_0;
else if (ci->rotations & RR_Rotate_90)
rotation = RR_Rotate_90;
else if (ci->rotations & RR_Rotate_180)
rotation = RR_Rotate_180;
else
rotation = RR_Rotate_270;
break;
}
}
}
XRRFreeCrtcInfo(ci);
}
if (free_crtc != None)
break;
}
if (free_crtc == None) {
wwarning("RandR: no free CRTC for output %s", o->name);
XRRFreeOutputInfo(oi);
continue;
}
/* Preferred mode is first in the list (per RandR spec) */
best_mode = oi->modes[0];
XRRFreeOutputInfo(oi);
/* Look up its pixel dimensions in sr->modes[] */
for (j = 0; j < sr->nmode; j++) {
if (sr->modes[j].id == best_mode) {
mode_w = (int)sr->modes[j].width;
mode_h = (int)sr->modes[j].height;
break;
}
}
if (mode_w == 0 || mode_h == 0)
continue;
/* Position it to the right of all currently active outputs, same as GNOME */
for (j = 0; j < state->n_outputs; j++) {
WRandROutput *a = &state->outputs[j];
if (a->crtc != None && a->w > 0) {
int right = a->x + a->w;
if (right > new_x)
new_x = right;
}
}
/* Expand virtual framebuffer if the new head exceeds current size */
new_vw = new_x + mode_w;
new_vh = mode_h > scr->scr_height ? mode_h : scr->scr_height;
if (new_vw > scr->scr_width || new_vh > scr->scr_height) {
int mm_w = (int)((long)WidthMMOfScreen(ScreenOfDisplay(dpy, scr->screen))
* new_vw / scr->scr_width);
int mm_h = (int)((long)HeightMMOfScreen(ScreenOfDisplay(dpy, scr->screen))
* new_vh / scr->scr_height);
XRRSetScreenSize(dpy, scr->root_win, new_vw, new_vh, mm_w, mm_h);
}
ret = XRRSetCrtcConfig(dpy, sr, free_crtc, sr->timestamp,
new_x, 0, best_mode, rotation,
&o->xid, 1);
if (ret == RRSetConfigSuccess) {
wwarning("RandR: auto-activated output %s at +%d+0", o->name, new_x);
activated = True;
} else {
wwarning("RandR: failed to auto-activate output %s", o->name);
}
}
return activated;
}
/* Synchronize RandR state with the X server */
static void wRandR_Update(WScreen *scr)
{
WRandRState *state = (WRandRState *)scr->randr_state;
XRRScreenResources *sr;
int i, j;
/* Assume all outputs removed until server confirms */
for (i = 0; i < state->n_outputs; i++)
state->outputs[i].stale = True;
/* Re-query server, update or add outputs */
sr = XRRGetScreenResourcesCurrent(dpy, scr->root_win);
if (!sr) {
wwarning("wRandR_Update: XRRGetScreenResourcesCurrent failed");
return;
}
for (i = 0; i < sr->noutput; i++) {
XRROutputInfo *oi;
WRandROutput *found = NULL;
oi = XRRGetOutputInfo(dpy, sr, sr->outputs[i]);
if (!oi)
continue;
/* Match by XID (stable across reconfigures) */
for (j = 0; j < state->n_outputs; j++) {
if (state->outputs[j].xid == sr->outputs[i]) {
found = &state->outputs[j];
break;
}
}
/* Append new output not seen before */
if (!found && state->n_outputs < RANDR_MAX_OUTPUTS) {
found = &state->outputs[state->n_outputs++];
memset(found, 0, sizeof(*found));
found->xid = sr->outputs[i];
strncpy(found->name, oi->name, sizeof(found->name) - 1);
}
if (found) {
found->stale = False;
found->connected = (oi->connection == RR_Connected);
found->crtc = oi->crtc;
found->w = 0;
found->h = 0;
found->rotation = RR_Rotate_0;
if (oi->crtc != None) {
XRRCrtcInfo *ci = XRRGetCrtcInfo(dpy, sr, oi->crtc);
if (ci) {
found->x = ci->x;
found->y = ci->y;
found->w = ci->width;
found->h = ci->height;
found->rotation = ci->rotation;
XRRFreeCrtcInfo(ci);
}
}
}
XRRFreeOutputInfo(oi);
}
/* When HotplugMonitor is enabled, actively manage CRTC lifecycle */
if (wPreferences.hotplug_monitor) {
Bool changed;
changed = wRandR_AutoDeactivate(scr, state, sr);
changed |= wRandR_AutoActivate(scr, state, sr);
if (changed) {
XRRFreeScreenResources(sr);
return;
}
}
XRRFreeScreenResources(sr);
/* Remove outputs not reported by server at all */
for (i = 0, j = 0; i < state->n_outputs; i++) {
if (!state->outputs[i].stale) {
if (j != i)
state->outputs[j] = state->outputs[i];
j++;
}
}
state->n_outputs = j;
/* Apply new geometry to the Xinerama head layer */
i = scr->xine_info.primary_head;
wRandR_ApplyToXinerama(scr, state);
/* Move the dock if needed */
if (scr->dock &&
((scr->xine_info.primary_head != i && wPreferences.keep_dock_on_primary_head) ||
scr->dock->x_pos < 0 ||
scr->dock->x_pos >= scr->scr_width))
wDockSwap(scr->dock);
/* Snap each workspace clip back onto a visible head if it ended up
* outside the (now smaller or rearranged) virtual framebuffer */
if (!wPreferences.flags.noclip) {
int k;
for (k = 0; k < scr->workspace_count; k++) {
WDock *clip = scr->workspaces[k]->clip;
if (clip)
wClipSnapToHead(clip);
}
}
/* Refresh usable areas and EWMH hints */
wScreenUpdateUsableArea(scr);
wNETWMUpdateWorkarea(scr);
/* Bring any windows that ended up in dead space to an active head */
wRandR_BringAllWindowsInside(scr);
/* Rearrange miniaturized and appicons into the icon yard */
wArrangeIcons(scr, True);
}
static void wRandRDebounceTimerFired(void *data)
{
WScreen *scr = (WScreen *)data;
scr->randr_debounce_timer = NULL;
wRandR_Update(scr);
}
/* end of local stuff */
void wRandRInit(WScreen *scr)
{
WRandRState *state;
if (!w_global.xext.randr.supported)
return;
state = wmalloc(sizeof(WRandRState));
memset(state, 0, sizeof(*state));
scr->randr_state = state;
wRandR_Scan(scr, state);
wRandR_ApplyToXinerama(scr, state);
}
void wRandRTeardown(WScreen *scr)
{
if (!scr->randr_state)
return;
if (scr->randr_debounce_timer) {
WMDeleteTimerHandler(scr->randr_debounce_timer);
scr->randr_debounce_timer = NULL;
}
wfree(scr->randr_state);
scr->randr_state = NULL;
}
void wRandRHandleNotify(WScreen *scr, XEvent *event)
{
if (!scr || !scr->randr_state)
return;
if (event->type == (w_global.xext.randr.event_base + RRScreenChangeNotify))
XRRUpdateConfiguration(event);
/* Debounce: cancel any pending timer, then restart it */
if (scr->randr_debounce_timer) {
WMDeleteTimerHandler(scr->randr_debounce_timer);
scr->randr_debounce_timer = NULL;
}
scr->randr_debounce_timer =
WMAddTimerHandler(RANDR_DEBOUNCE_DELAY, wRandRDebounceTimerFired, scr);
}
#endif /* USE_RANDR */
+36
View File
@@ -0,0 +1,36 @@
/* randr.h - RandR multi-monitor support
*
* Window Maker window manager
*
* Copyright (c) 2026 Window Maker Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _WMRANDR_H_
#define _WMRANDR_H_
#ifdef USE_RANDR
#include <X11/extensions/Xrandr.h>
#include "screen.h"
void wRandRInit(WScreen *scr);
void wRandRTeardown(WScreen *scr);
void wRandRHandleNotify(WScreen *scr, XEvent *event);
#endif /* USE_RANDR */
#endif /* _WMRANDR_H_ */
+19 -2
View File
@@ -34,7 +34,7 @@
#include <X11/Xatom.h>
#include <X11/XKBlib.h>
#ifdef USE_RANDR
#include <X11/extensions/Xrandr.h>
#include "randr.h"
#endif
#include <wraster.h>
@@ -642,6 +642,10 @@ WScreen *wScreenInit(int screen_number)
scr->usableArea[i].y2 = scr->totalUsableArea[i].y2 = rect.pos.y + rect.size.height;
}
#ifdef USE_RANDR
wRandRInit(scr);
#endif
scr->fakeGroupLeaders = WMCreateArray(16);
CantManageScreen = 0;
@@ -669,7 +673,10 @@ WScreen *wScreenInit(int screen_number)
#ifdef USE_RANDR
if (w_global.xext.randr.supported)
XRRSelectInput(dpy, scr->root_win, RRScreenChangeNotifyMask);
XRRSelectInput(dpy, scr->root_win,
RRScreenChangeNotifyMask |
RRCrtcChangeNotifyMask |
RROutputChangeNotifyMask);
#endif
XSync(dpy, False);
@@ -1367,3 +1374,13 @@ void ScreenCapture(WScreen *scr, int mode)
wfree(filepath);
wfree(screenshot_dir);
}
void wScreenDestroy(WScreen *scr)
{
#ifdef USE_RANDR
wRandRTeardown(scr);
#else
/* Parameter not used, but tell the compiler that it is ok */
(void) scr;
#endif
}
+16 -1
View File
@@ -59,6 +59,14 @@ typedef struct WDrawerChain {
struct WDrawerChain *next;
} WDrawerChain;
typedef enum {
MARK_CAPTURE_IDLE = 0,
MARK_CAPTURE_SET = 1,
MARK_CAPTURE_BRING = 2,
MARK_CAPTURE_JUMP = 3,
MARK_CAPTURE_SWAP = 4
} WMarkCaptureMode;
/*
* each WScreen is saved into a context associated with its root window
*/
@@ -299,6 +307,11 @@ typedef struct _WScreen {
/* for hot-corners delay */
WMHandlerID *hot_corner_timer;
#ifdef USE_RANDR
WMHandlerID *randr_debounce_timer;
void *randr_state;
#endif
/* for window shortcuts */
WMArray *shortcutWindows[MAX_WINDOW_SHORTCUTS];
@@ -327,6 +340,8 @@ typedef struct _WScreen {
unsigned int jump_back_pending:1;
unsigned int ignore_focus_events:1;
unsigned int in_hot_corner:3;
unsigned int mark_capture_mode:3; /* one of WMarkCaptureMode */
} flags;
} WScreen;
@@ -345,7 +360,7 @@ WScreen *wScreenWithNumber(int i);
WScreen *wScreenForRootWindow(Window window); /* window must be valid */
WScreen *wScreenForWindow(Window window); /* slower than above functions */
void wScreenFinish(WScreen *scr);
void wScreenDestroy(WScreen *scr);
void wScreenUpdateUsableArea(WScreen *scr);
void create_logo_image(WScreen *scr);
+20
View File
@@ -97,6 +97,7 @@ static WMPropList *sMaximized;
static WMPropList *sHidden;
static WMPropList *sGeometry;
static WMPropList *sShortcutMask;
static WMPropList *sMarkKey;
static WMPropList *sDock;
static WMPropList *sYes, *sNo;
@@ -118,6 +119,7 @@ static void make_keys(void)
sGeometry = WMCreatePLString("Geometry");
sDock = WMCreatePLString("Dock");
sShortcutMask = WMCreatePLString("ShortcutMask");
sMarkKey = WMCreatePLString("MarkKey");
sYes = WMCreatePLString("Yes");
sNo = WMCreatePLString("No");
@@ -165,6 +167,7 @@ static WMPropList *makeWindowState(WWindow * wwin, WApplication * wapp)
WMPropList *win_state, *cmd, *name, *workspace;
WMPropList *shaded, *miniaturized, *maximized, *hidden, *geometry;
WMPropList *dock, *shortcut;
WMPropList *mark_key_pl = NULL;
if (wwin->orig_main_window != None && wwin->orig_main_window != wwin->client_win)
win = wwin->orig_main_window;
@@ -215,6 +218,9 @@ static WMPropList *makeWindowState(WWindow * wwin, WApplication * wapp)
snprintf(buffer, sizeof(buffer), "%u", mask);
shortcut = WMCreatePLString(buffer);
if (wwin->mark_key_label)
mark_key_pl = WMCreatePLString(wwin->mark_key_label);
win_state = WMCreatePLDictionary(sName, name,
sCommand, cmd,
sWorkspace, workspace,
@@ -224,6 +230,11 @@ static WMPropList *makeWindowState(WWindow * wwin, WApplication * wapp)
sHidden, hidden,
sShortcutMask, shortcut, sGeometry, geometry, NULL);
if (mark_key_pl) {
WMPutInPLDictionary(win_state, sMarkKey, mark_key_pl);
WMReleasePropList(mark_key_pl);
}
WMReleasePropList(name);
WMReleasePropList(cmd);
WMReleasePropList(workspace);
@@ -418,6 +429,13 @@ static WSavedState *getWindowState(WScreen * scr, WMPropList * win_state)
state->window_shortcuts = mask;
}
value = WMGetFromPLDictionary(win_state, sMarkKey);
if (value != NULL && WMIsPLString(value)) {
char *s = WMGetFromPLString(value);
if (s && *s)
state->mark_key = wstrdup(s);
}
value = WMGetFromPLDictionary(win_state, sGeometry);
if (value && WMIsPLString(value)) {
if (!(sscanf(WMGetFromPLString(value), "%ix%i+%i+%i",
@@ -531,6 +549,8 @@ void wSessionRestoreState(WScreen *scr)
} else if ((pid = execCommand(scr, command)) > 0) {
wWindowAddSavedState(instance, class, command, pid, state);
} else {
if (state->mark_key)
wfree(state->mark_key);
wfree(state);
}
+3
View File
@@ -36,6 +36,7 @@
#include "winspector.h"
#include "wmspec.h"
#include "colormap.h"
#include "screen.h"
#include "shutdown.h"
@@ -81,6 +82,7 @@ void Shutdown(WShutdownMode mode)
wipeDesktop(scr);
else
RestoreDesktop(scr);
wScreenDestroy(scr);
}
}
ExecExitScript();
@@ -103,6 +105,7 @@ void Shutdown(WShutdownMode mode)
kill(scr->helper_pid, SIGKILL);
wScreenSaveState(scr);
RestoreDesktop(scr);
wScreenDestroy(scr);
}
}
break;
+19 -1
View File
@@ -387,6 +387,7 @@ static char *atomNames[] = {
"_WINDOWMAKER_COMMAND",
"_WINDOWMAKER_ICON_SIZE",
"_WINDOWMAKER_ICON_TILE",
"_WINDOWMAKER_MARK_KEY",
GNUSTEP_WM_ATTR_NAME,
GNUSTEP_WM_MINIATURIZE_WINDOW,
@@ -474,6 +475,7 @@ void StartUp(Bool defaultScreenOnly)
w_global.atom.desktop.gtk_object_path = atom[20];
w_global.atom.wm.ignore_focus_events = atom[21];
w_global.atom.wmaker.mark_key = atom[22];
#ifdef USE_DOCK_XDND
wXDNDInitializeAtoms();
@@ -600,7 +602,23 @@ void StartUp(Bool defaultScreenOnly)
#endif
#ifdef USE_RANDR
w_global.xext.randr.supported = XRRQueryExtension(dpy, &w_global.xext.randr.event_base, &j);
{
int rr_major = 0, rr_minor = 0;
Bool rr_ext = XRRQueryExtension(dpy, &w_global.xext.randr.event_base, &j);
Bool rr_ver = rr_ext && XRRQueryVersion(dpy, &rr_major, &rr_minor);
if (rr_ver && (rr_major > 1 || (rr_major == 1 && rr_minor >= 3))) {
w_global.xext.randr.supported = 1;
} else {
w_global.xext.randr.supported = 0;
if (!rr_ext)
wwarning(_("RandR extension is not available"));
else if (!rr_ver)
wwarning(_("RandR version check failed, RandR disabled"));
else
wwarning(_("RandR version %d.%d found but RandR version >=1.3 required"), rr_major, rr_minor);
}
}
#endif
w_global.xext.xkb.supported = XkbQueryExtension(dpy, NULL, &w_global.xext.xkb.event_base, NULL, NULL, NULL);
+28 -18
View File
@@ -44,6 +44,8 @@
((w)->wm_gnustep_attr->window_level == WMMainMenuWindowLevel || \
(w)->wm_gnustep_attr->window_level == WMSubmenuWindowLevel))
#define MAX_RTEXT_LENGTH (MAX_WORKSPACENAME_WIDTH + MAX_SHORTCUT_LENGTH + 16)
static int initialized = 0;
static void observer(void *self, WMNotification * notif);
static void wsobserver(void *self, WMNotification * notif);
@@ -214,6 +216,26 @@ static int menuIndexForWindow(WMenu * menu, WWindow * wwin, int old_pos)
return idx;
}
static void fillRtext(char *buf, size_t bufsz, WWindow *wwin, WScreen *scr)
{
char *mlbl = wwin->mark_key_label ? GetShortcutString(wwin->mark_key_label) : NULL;
if (IS_OMNIPRESENT(wwin)) {
if (mlbl)
snprintf(buf, bufsz, "[%s] [*]", mlbl);
else
snprintf(buf, bufsz, "[*]");
} else {
if (mlbl)
snprintf(buf, bufsz, "[%s] [%s]", mlbl,
scr->workspaces[wwin->frame->workspace]->name);
else
snprintf(buf, bufsz, "[%s]",
scr->workspaces[wwin->frame->workspace]->name);
}
wfree(mlbl);
}
/*
* Update switch menu
*/
@@ -263,12 +285,8 @@ void UpdateSwitchMenu(WScreen * scr, WWindow * wwin, int action)
entry->icon = switchMenuIconForWindow(scr, wwin);
entry->flags.indicator = 1;
entry->rtext = wmalloc(MAX_WORKSPACENAME_WIDTH + 8);
if (IS_OMNIPRESENT(wwin))
snprintf(entry->rtext, MAX_WORKSPACENAME_WIDTH, "[*]");
else
snprintf(entry->rtext, MAX_WORKSPACENAME_WIDTH, "[%s]",
scr->workspaces[wwin->frame->workspace]->name);
entry->rtext = wmalloc(MAX_RTEXT_LENGTH);
fillRtext(entry->rtext, MAX_RTEXT_LENGTH, wwin, scr);
if (wwin->flags.hidden) {
entry->flags.indicator_type = MI_HIDDEN;
@@ -311,6 +329,8 @@ void UpdateSwitchMenu(WScreen * scr, WWindow * wwin, int action)
t = ShrinkString(scr->menu_entry_font, title, MAX_WINDOWLIST_WIDTH);
entry->text = t;
fillRtext(entry->rtext, MAX_RTEXT_LENGTH, wwin, scr);
wMenuRealize(switchmenu);
checkVisibility = 1;
break;
@@ -322,13 +342,7 @@ void UpdateSwitchMenu(WScreen * scr, WWindow * wwin, int action)
WPixmap *ipix;
int it, ion;
if (IS_OMNIPRESENT(wwin)) {
snprintf(entry->rtext, MAX_WORKSPACENAME_WIDTH, "[*]");
} else {
snprintf(entry->rtext, MAX_WORKSPACENAME_WIDTH,
"[%s]",
scr->workspaces[wwin->frame->workspace]->name);
}
fillRtext(entry->rtext, MAX_RTEXT_LENGTH, wwin, scr);
rt = entry->rtext;
entry->rtext = NULL;
@@ -404,11 +418,7 @@ static void UpdateSwitchMenuWorkspace(WScreen *scr, int workspace)
wwin = (WWindow *) menu->entries[i]->clientdata;
if (wwin->frame->workspace == workspace && !IS_OMNIPRESENT(wwin)) {
if (IS_OMNIPRESENT(wwin))
snprintf(menu->entries[i]->rtext, MAX_WORKSPACENAME_WIDTH, "[*]");
else
snprintf(menu->entries[i]->rtext, MAX_WORKSPACENAME_WIDTH, "[%s]",
scr->workspaces[wwin->frame->workspace]->name);
fillRtext(menu->entries[i]->rtext, MAX_RTEXT_LENGTH, wwin, scr);
menu->flags.realized = 0;
}
}
+116 -1
View File
@@ -75,6 +75,7 @@
#include "startup.h"
#include "winmenu.h"
#include "osdep.h"
#include "switchmenu.h"
#ifdef USE_MWM_HINTS
# include "motif.h"
@@ -200,6 +201,10 @@ void wWindowDestroy(WWindow *wwin)
}
}
/* clean up any mark assigned to this window */
if (wwin->mark_key_label != NULL)
wWindowUnsetMark(wwin);
if (wwin->fake_group && wwin->fake_group->retainCount > 0) {
wwin->fake_group->retainCount--;
if (wwin->fake_group->retainCount == 0 && wwin->fake_group->leader != None) {
@@ -465,6 +470,35 @@ void wWindowSetupInitialAttributes(WWindow *wwin, int *level, int *workspace)
wwin->client_flags.no_focusable = 1;
}
/*
* Returns True if every pixel of 'wwin' is covered by at least one other
* mapped window on the same workspace, making it invisible to the user
*/
Bool wWindowIsFullyCovered(WWindow *wwin)
{
WScreen *scr = wwin->screen_ptr;
int cx = wwin->frame_x;
int cy = wwin->frame_y;
int cright = cx + (int)wwin->frame->core->width;
int cbottom = cy + (int)wwin->frame->core->height;
WWindow *w;
for (w = scr->focused_window; w != NULL; w = w->prev) {
if (w == wwin)
continue;
if (!w->flags.mapped)
continue;
if (!w->frame || w->frame->workspace != scr->current_workspace)
continue;
if (w->frame_x <= cx &&
w->frame_y <= cy &&
w->frame_x + (int)w->frame->core->width >= cright &&
w->frame_y + (int)w->frame->core->height >= cbottom)
return True;
}
return False;
}
Bool wWindowObscuresWindow(WWindow *wwin, WWindow *obscured)
{
int w1, h1, w2, h2;
@@ -1006,9 +1040,18 @@ WWindow *wManageWindow(WScreen *scr, Window window)
}
}
if (wstate != NULL)
/* restore mark: prefer session state, fall back to warm-restart hint */
if (win_state != NULL && win_state->state->mark_key != NULL)
wWindowSetMark(wwin, win_state->state->mark_key);
else if (wstate != NULL && wstate->mark_key != NULL)
wWindowSetMark(wwin, wstate->mark_key);
if (wstate != NULL) {
if (wstate->mark_key != NULL)
wfree(wstate->mark_key);
wfree(wstate);
}
}
/* don't let transients start miniaturized if their owners are not */
if (transientOwner && !transientOwner->flags.miniaturized && wwin->flags.miniaturized && !withdraw) {
@@ -2219,6 +2262,14 @@ void wWindowConfigure(WWindow *wwin, int req_x, int req_y, int req_width, int re
wFrameWindowConfigure(wwin->frame, req_x, req_y, req_width, h);
}
/*
* When the frame is resized/moved, the X server repositions a client
* with non-NorthWest gravity inside the frame to compensate (visible as
* a GravityNotify with xev), leaving it at the wrong offset.
*/
if (wwin->normal_hints->win_gravity != NorthWestGravity)
XMoveWindow(dpy, wwin->client_win, 0, wwin->frame->top_width);
if (!(req_height > wwin->frame->core->height || req_width > wwin->frame->core->width))
XResizeWindow(dpy, wwin->client_win, req_width, req_height);
@@ -2294,6 +2345,26 @@ void wWindowMove(WWindow *wwin, int req_x, int req_y)
#endif
}
/* Move the window to the nearest on-screen position if its stored
* frame origin falls in dead space (for example when a RandR monitor
* was removed while the window was miniaturized or hidden) */
void wWindowSnapToHead(WWindow *wwin)
{
#ifdef USE_RANDR
int bw = HAS_BORDER(wwin) ? wwin->screen_ptr->frame_border_width : 0;
int rx = wwin->frame_x - bw;
int ry = wwin->frame_y - bw;
if (wScreenBringInside(wwin->screen_ptr, &rx, &ry,
wwin->frame->core->width + 2 * bw,
wwin->frame->core->height + 2 * bw))
wWindowMove(wwin, rx + bw, ry + bw);
#else
/* Parameter not used, but tell the compiler that it is ok */
(void) wwin;
#endif
}
void wWindowUpdateButtonImages(WWindow *wwin)
{
WScreen *scr = wwin->screen_ptr;
@@ -2499,6 +2570,14 @@ void wWindowSaveState(WWindow *wwin)
XChangeProperty(dpy, wwin->client_win, w_global.atom.wmaker.state,
w_global.atom.wmaker.state, 32, PropModeReplace, (unsigned char *)data, 10);
if (wwin->mark_key_label != NULL)
XChangeProperty(dpy, wwin->client_win, w_global.atom.wmaker.mark_key,
XA_STRING, 8, PropModeReplace,
(unsigned char *)wwin->mark_key_label,
strlen(wwin->mark_key_label));
else
XDeleteProperty(dpy, wwin->client_win, w_global.atom.wmaker.mark_key);
}
static int getSavedState(Window window, WSavedState ** state)
@@ -2508,6 +2587,7 @@ static int getSavedState(Window window, WSavedState ** state)
unsigned long nitems_ret;
unsigned long bytes_after_ret;
long *data;
unsigned char *mk_data = NULL;
if (XGetWindowProperty(dpy, window, w_global.atom.wmaker.state, 0, 10,
True, w_global.atom.wmaker.state,
@@ -2535,6 +2615,15 @@ static int getSavedState(Window window, WSavedState ** state)
XFree(data);
(*state)->mark_key = NULL;
if (XGetWindowProperty(dpy, window, w_global.atom.wmaker.mark_key, 0, 256,
True, XA_STRING, &type_ret, &fmt_ret, &nitems_ret, &bytes_after_ret,
&mk_data) == Success && mk_data && nitems_ret > 0 && type_ret == XA_STRING)
(*state)->mark_key = wstrdup((char *)mk_data);
if (mk_data)
XFree(mk_data);
return 1;
}
@@ -2861,6 +2950,9 @@ static void release_wwindowstate(WWindowState *wstate)
if (wstate->command)
wfree(wstate->command);
if (wstate->state && wstate->state->mark_key)
wfree(wstate->state->mark_key);
wfree(wstate->state);
wfree(wstate);
}
@@ -2874,6 +2966,29 @@ void wWindowSetOmnipresent(WWindow *wwin, Bool flag)
WMPostNotificationName(WMNChangedState, wwin, "omnipresent");
}
void wWindowSetMark(WWindow *wwin, const char *label)
{
/* Remove any previous mark first */
if (wwin->mark_key_label != NULL)
wWindowUnsetMark(wwin);
wwin->mark_key_label = wstrdup(label);
UpdateSwitchMenu(wwin->screen_ptr, wwin, ACTION_CHANGE);
}
void wWindowUnsetMark(WWindow *wwin)
{
if (wwin->mark_key_label == NULL)
return;
wfree(wwin->mark_key_label);
wwin->mark_key_label = NULL;
UpdateSwitchMenu(wwin->screen_ptr, wwin, ACTION_CHANGE);
}
static void resizebarMouseDown(WCoreWindow *sender, void *data, XEvent *event)
{
WWindow *wwin = data;
+8
View File
@@ -299,6 +299,7 @@ typedef struct WWindow {
int icon_w, icon_h;
RImage *net_icon_image; /* Window Image */
Atom type;
char *mark_key_label; /* Vim-like Window Marking */
} WWindow;
#define HAS_TITLEBAR(w) (!(WFLAGP((w), no_titlebar) || (w)->flags.fullscreen))
@@ -323,6 +324,7 @@ typedef struct WSavedState {
unsigned int w;
unsigned int h;
unsigned window_shortcuts; /* mask like 1<<shortcut_number */
char *mark_key; /* serialised mark key label */
} WSavedState;
typedef struct WWindowState {
@@ -362,6 +364,7 @@ void wWindowConfigure(WWindow *wwin, int req_x, int req_y,
int req_width, int req_height);
void wWindowMove(WWindow *wwin, int req_x, int req_y);
void wWindowSnapToHead(WWindow *wwin);
void wWindowSynthConfigureNotify(WWindow *wwin);
@@ -402,10 +405,15 @@ WMagicNumber wWindowGetSavedState(Window win);
void wWindowDeleteSavedState(WMagicNumber id);
Bool wWindowIsFullyCovered(WWindow *wwin);
Bool wWindowObscuresWindow(WWindow *wwin, WWindow *obscured);
void wWindowSetOmnipresent(WWindow *wwin, Bool flag);
/* Vim-like window marking management */
void wWindowSetMark(WWindow *wwin, const char *label);
void wWindowUnsetMark(WWindow *wwin);
#ifdef XKB_BUTTON_HINT
void wWindowGetLanguageLabel(int group_index, char *label);
#endif
+3 -3
View File
@@ -1443,10 +1443,10 @@ static void create_tab_app_specific(WWindow *wwin, InspectorPanel *panel, int fr
if (WFLAGP(wwin, emulate_appicon)) {
WMSetButtonEnabled(panel->appChk[1], False);
WMSetButtonEnabled(panel->moreChk[7], True);
WMSetButtonEnabled(panel->moreChk[8], True);
} else {
WMSetButtonEnabled(panel->appChk[1], True);
WMSetButtonEnabled(panel->moreChk[7], False);
WMSetButtonEnabled(panel->moreChk[8], False);
}
} else {
if ((wwin->transient_for != None && wwin->transient_for != scr->root_win)
@@ -1455,7 +1455,7 @@ static void create_tab_app_specific(WWindow *wwin, InspectorPanel *panel, int fr
else
tmp = True;
WMSetButtonEnabled(panel->moreChk[7], tmp);
WMSetButtonEnabled(panel->moreChk[8], tmp);
WMSetPopUpButtonItemEnabled(panel->pagePopUp, 4, False);
panel->appFrm = NULL;
+2 -2
View File
@@ -1524,7 +1524,7 @@ void wNETWMCheckClientHints(WWindow *wwin, int *layer, int *workspace)
*workspace = desktop;
}
if (XGetWindowProperty(dpy, wwin->client_win, net_wm_state, 0, 1, False,
if (XGetWindowProperty(dpy, wwin->client_win, net_wm_state, 0, 1024L, False,
XA_ATOM, &type_ret, &fmt_ret, &nitems_ret,
&bytes_after_ret, (unsigned char **)&data) == Success && data) {
@@ -1535,7 +1535,7 @@ void wNETWMCheckClientHints(WWindow *wwin, int *layer, int *workspace)
XFree(data);
}
if (XGetWindowProperty(dpy, wwin->client_win, net_wm_window_type, 0, 1, False,
if (XGetWindowProperty(dpy, wwin->client_win, net_wm_window_type, 0, 1024L, False,
XA_ATOM, &type_ret, &fmt_ret, &nitems_ret,
&bytes_after_ret, (unsigned char **)&data) == Success && data) {
+7 -1
View File
@@ -4,10 +4,16 @@ AUTOMAKE_OPTIONS = no-dependencies
EXTRA_DIST = notest.c
noinst_PROGRAMS = wtest
noinst_PROGRAMS = wtest wm_fsm_test
wtest_SOURCES = wtest.c
wtest_LDADD = $(top_builddir)/wmlib/libWMaker.la @XLFLAGS@ @XLIBS@
AM_CPPFLAGS = -g -D_BSD_SOURCE @XCFLAGS@ -I$(top_srcdir)/wmlib
wm_fsm_test_SOURCES = wm_fsm_test.c
wm_fsm_test_CPPFLAGS = $(shell pkg-config --cflags gtk+-3.0)
wm_fsm_test_LDFLAGS = $(shell pkg-config --libs gtk+-3.0) -lX11
+136
View File
@@ -0,0 +1,136 @@
/* test application that demonstrates _NET_WM_FULLSCREEN_MONITORS
*
* how to run it:
* G_MESSAGES_DEBUG=all ./wm_fsm_test
*/
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <string.h>
static GtkWidget* window;
static gboolean print_final_size(gpointer data)
{
(void)data;
gint w, h;
gtk_window_get_size(GTK_WINDOW(window), &w, &h);
g_debug("final window size: %dx%d", w, h);
return FALSE;
}
static gboolean on_configure_after_unfullscreen(GtkWidget *widget, GdkEventConfigure *event, gpointer data)
{
(void)event;
(void)data;
g_signal_handlers_disconnect_by_func(widget, on_configure_after_unfullscreen, data);
g_idle_add(print_final_size, NULL);
return FALSE;
}
static gboolean fullscreen(gpointer data)
{
(void)data;
g_debug("fullscreen()");
gtk_window_fullscreen(GTK_WINDOW(window));
return FALSE;
}
static gboolean on_window_state_event(GtkWidget *widget, GdkEventWindowState *event, gpointer data)
{
(void)widget;
(void)data;
if ((event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) &&
!(event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN)) {
/* Force geometry back */
gtk_window_move(GTK_WINDOW(window), 0, 0);
gtk_window_resize(GTK_WINDOW(window), 800, 600);
g_signal_connect(window, "configure-event", G_CALLBACK(on_configure_after_unfullscreen), NULL);
}
return FALSE;
}
static gboolean unfullscreen(gpointer data)
{
(void)data;
g_debug("unfullscreen()");
gtk_window_unfullscreen(GTK_WINDOW(window));
return FALSE;
}
static gboolean switch_monitors(gpointer data)
{
static const long monitors[4][4] = {
{0, 0, 0, 1},
{0, 0, 0, 0},
{1, 1, 1, 1},
{0, 0, 0, 1}
};
static const char* desc[4] = {
"Window should be covering both heads 1 and 2\n",
"Window should be covering just the first head\n",
"Window should be covering just the second head\n",
"Window should be covering both heads 1 and 2\n"
};
guint index = GPOINTER_TO_UINT(data);
g_debug("%s", desc[index]);
GdkDisplay *display = gdk_display_get_default();
GdkWindow *gwin = gtk_widget_get_window(window);
if (!gwin) {
g_warning("switch_monitors: window not realized yet");
return FALSE;
}
XClientMessageEvent xclient;
memset(&xclient, 0, sizeof(xclient));
xclient.type = ClientMessage;
xclient.window = GDK_WINDOW_XID(gwin);
xclient.message_type = gdk_x11_get_xatom_by_name_for_display(display, "_NET_WM_FULLSCREEN_MONITORS");
xclient.format = 32;
xclient.data.l[0] = monitors[index][0];
xclient.data.l[1] = monitors[index][1];
xclient.data.l[2] = monitors[index][2];
xclient.data.l[3] = monitors[index][3];
xclient.data.l[4] = 1;
XSendEvent(GDK_WINDOW_XDISPLAY(gwin),
GDK_WINDOW_XID(gdk_get_default_root_window()),
False,
SubstructureRedirectMask | SubstructureNotifyMask,
(XEvent *) &xclient);
return FALSE;
}
static gboolean quit(gpointer data)
{
(void)data;
gtk_main_quit();
return FALSE;
}
int main(int argc, char** argv)
{
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
gtk_widget_show(window);
g_signal_connect(window, "window-state-event", G_CALLBACK(on_window_state_event), NULL);
g_timeout_add(1000, (GSourceFunc) fullscreen, NULL);
g_timeout_add(5000, (GSourceFunc) switch_monitors, GUINT_TO_POINTER(0));
g_timeout_add(10000, (GSourceFunc) switch_monitors, GUINT_TO_POINTER(1));
g_timeout_add(15000, (GSourceFunc) switch_monitors, GUINT_TO_POINTER(2));
g_timeout_add(20000, (GSourceFunc) switch_monitors, GUINT_TO_POINTER(3));
g_timeout_add(25000, (GSourceFunc) unfullscreen, NULL);
g_timeout_add(30000, (GSourceFunc) quit, NULL);
gtk_main();
}