1
0
mirror of https://github.com/gryf/wmaker.git synced 2026-04-29 12:04:06 +02:00

Compare commits

..

12 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
26 changed files with 890 additions and 132 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)
+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") },
+1
View File
@@ -609,6 +609,7 @@ extern struct wmaker_global_variables {
Atom icon_size;
Atom icon_tile;
Atom mark_key;
} wmaker;
} atom;
+94
View File
@@ -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)
{
+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);
+20
View File
@@ -721,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,
@@ -823,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,
+157
View File
@@ -1471,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;
@@ -1726,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;
@@ -1975,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;
}
}
@@ -1990,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);
+10
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
*/
@@ -332,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;
+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);
}
+2
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();
+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;
}
}
+96 -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,8 +1040,17 @@ 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 */
@@ -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);
@@ -2519,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)
@@ -2528,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,
@@ -2555,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;
}
@@ -2881,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);
}
@@ -2894,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;
+7
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 {
@@ -403,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();
}