mirror of
https://github.com/gryf/wmaker.git
synced 2026-04-30 12:34:07 +02:00
Compare commits
17 Commits
329f82f6e7
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 931186bd18 | |||
| 5b631cad93 | |||
| ee9fd77a56 | |||
| 62e341b5cc | |||
| fa49b7c003 | |||
| 6dbc6c95ab | |||
| 4babc0e422 | |||
| 33fba87ed1 | |||
| a3c02a22bd | |||
| 0d74066aea | |||
| 26a296db23 | |||
| ac1fa7fc6d | |||
| 1f03c13f4d | |||
| 8e84264036 | |||
| 4b4abf4c50 | |||
| a631e3060e | |||
| 6c5c3e6181 |
@@ -1,10 +1,183 @@
|
|||||||
NEWS for veteran Window Maker users
|
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
|
-- 0.96.0
|
||||||
|
|
||||||
Hot Corners feature
|
Hot Corners feature
|
||||||
--------------------------
|
-------------------
|
||||||
|
|
||||||
Screen corners can be assigned an external command to be
|
Screen corners can be assigned an external command to be
|
||||||
executed when the mouse pointer is entering those areas.
|
executed when the mouse pointer is entering those areas.
|
||||||
@@ -103,12 +276,12 @@ file.
|
|||||||
Alternative way for traverse half-maximized windows
|
Alternative way for traverse half-maximized windows
|
||||||
---------------------------------------------------
|
---------------------------------------------------
|
||||||
|
|
||||||
For now, there could be three possible state of the window while using
|
For now, there could be three possible states of the window while using
|
||||||
half-maximized feature. Unmaximized window have its state saved during that
|
the half-maximized feature. Unmaximized windows have their state saved during
|
||||||
process, which was use to unmaximize it. For example, if there is a window,
|
that process, which is used to unmaximize them. For example, if there is a
|
||||||
which is maximized on the half left side of the screen, and while requesting
|
window that is maximized on the left half of the screen, and while requesting
|
||||||
left-half-maximized, it become unmaximized with size and dimension restored to
|
left-half-maximize it becomes unmaximized with its size and dimensions
|
||||||
original state.
|
restored to the original state.
|
||||||
|
|
||||||
By setting "AlternativeHalfMaximized" option to "Yes"
|
By setting "AlternativeHalfMaximized" option to "Yes"
|
||||||
~/GNUstep/Defaults/WindowMaker config file (or by using WPrefs.app and option
|
~/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
|
Further invoking right-half-maximize will do nothing. Note, that the window always
|
||||||
can be unmaximzied using appropriate keyboard shortcut or by selecting
|
can be unmaximized using an appropriate keyboard shortcut or by selecting
|
||||||
"Unmaximize" from window menu.
|
"Unmaximize" from the window menu.
|
||||||
|
|
||||||
Going to opposite direction by invoking left-half-maximize, will make the window
|
Going to opposite direction by invoking left-half-maximize, will make the window
|
||||||
maximized in both, vertical and horizontal directions:
|
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
|
It can be right-half-maximized (using the previously defined key combination), so it
|
||||||
become:
|
becomes:
|
||||||
|
|
||||||
┌┬───────┬────────┬─────────────────┬┐
|
┌┬───────┬────────┬─────────────────┬┐
|
||||||
├┘ ├────────┤ ├┤
|
├┘ ├────────┤ ├┤
|
||||||
@@ -242,7 +415,7 @@ become:
|
|||||||
└┴┴───────────────┴┴┴┴───────────────┘
|
└┴┴───────────────┴┴┴┴───────────────┘
|
||||||
|
|
||||||
In this example there is an assumption that WindowMaker is configured, that
|
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
|
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
|
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
|
Where the window is restored to its size (but not position, since it is on a different
|
||||||
head now). Moving window in directions like left-half-maximize,
|
head now). Moving a window in directions like left-half-maximize,
|
||||||
top-half-maximize and bottom-half-maximize will behave in similar way depending
|
top-half-maximize and bottom-half-maximize will behave in similar way depending
|
||||||
on the layout of displays.
|
on the layout of displays.
|
||||||
|
|
||||||
@@ -352,12 +525,12 @@ There are four choices:
|
|||||||
geometry is restored.
|
geometry is restored.
|
||||||
* "Unmaximize" ("...consider the window unmaximized") causes a maximized window
|
* "Unmaximize" ("...consider the window unmaximized") causes a maximized window
|
||||||
to be moved when dragged and remains partially maximized, i.e., it keeps its
|
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.
|
be immediately re-maximized.
|
||||||
* "NoMove" ("...do not move the window") prevents a maximized window from being
|
* "NoMove" ("...do not move the window") prevents a maximized window from being
|
||||||
moved when dragged.
|
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"
|
WPrefs.app, the option to "Open dialogs in the same workspace as their owners"
|
||||||
(which sets the "OpenTransientOnOwnerWorkspace" option from
|
(which sets the "OpenTransientOnOwnerWorkspace" option from
|
||||||
~/GNUstep/Defaults/WindowMaker) has been moved to "Expert User Preferences".
|
~/GNUstep/Defaults/WindowMaker) has been moved to "Expert User Preferences".
|
||||||
@@ -1157,7 +1330,7 @@ Others
|
|||||||
|
|
||||||
- added DONT_SCALE_ICONS compile time option
|
- added DONT_SCALE_ICONS compile time option
|
||||||
- added --dont-restore cmd line option. When passed to wmaker, it
|
- 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
|
--- 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.
|
raising/lowering there is a time threshold before raising/lowering.
|
||||||
The thresholds can be changed in wconfig.h by changing one or both of
|
The thresholds can be changed in wconfig.h by changing one or both of
|
||||||
AUTO_LOWER_DELAY and AUTO_RAISE_DELAY (expressed in milliseconds).
|
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
|
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,
|
big value will make the Clip practically not auto-raise unless clicked,
|
||||||
but to be automatically lowered after AUTO_LOWER_DELAY (ms) when leaved.
|
but it will still be automatically lowered after AUTO_LOWER_DELAY (ms) when left.
|
||||||
|
|
||||||
|
|
||||||
New ThemePack Format
|
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
|
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
|
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.
|
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
|
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.
|
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.
|
be used further until the destination is reached.
|
||||||
|
|
||||||
This way a nice deceleration effect is achieved, since while the icon
|
This way a nice deceleration effect is achieved, since while the icon
|
||||||
approaches destination, it will use smaller steps, gibing the illusion that
|
approaches the destination, it will use smaller steps, giving the illusion that
|
||||||
the icons is moving slower.
|
the icons are moving slower.
|
||||||
|
|
||||||
IconSlideDelay will give the pause between steps, and is expressed in ms.
|
IconSlideDelay will give the pause between steps, and is expressed in ms.
|
||||||
|
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ Performance Tuning
|
|||||||
==================
|
==================
|
||||||
|
|
||||||
If you want to diminish Window Maker's memory usage and improve performance,
|
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
|
- use solid textures for everything, mainly title bars and menus. If you want a
|
||||||
nice looking desktop, use the Traditional style.
|
nice looking desktop, use the Traditional style.
|
||||||
@@ -240,7 +240,7 @@ pointer faster;
|
|||||||
clicked with the 0 (Ins) key.
|
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.
|
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
|
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
|
produced by Window Maker when it crashes. It should have been installed without
|
||||||
stripping too.
|
being stripped.
|
||||||
|
|
||||||
To compile wmaker with debugging information:
|
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
|
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.
|
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.
|
trademarks are property of their respective owners.
|
||||||
|
|
||||||
The authors reserve the right to make changes in the software without prior
|
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
|
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
|
be found in po/README and Window Maker/README
|
||||||
|
|
||||||
If you have any comments, fixes and bug reports (filled BUGFORMs) send them
|
If you have any comments, fixes and bug reports (filled BUGFORMs) send them
|
||||||
|
|||||||
@@ -686,6 +686,37 @@ void W_BroadcastMessage(W_View *targetParent, XEvent *event);
|
|||||||
|
|
||||||
void W_DispatchMessage(W_View *target, 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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-4
@@ -122,11 +122,14 @@ static int fitText(const char *text, WMFont * font, int width, int wrap)
|
|||||||
word1 = word2;
|
word1 = word2;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = word1; i < word2; i++) {
|
/* Advance character by character (not byte by byte) */
|
||||||
w = WMWidthOfString(font, text, i);
|
i = word1;
|
||||||
if (w > width) {
|
while (i < word2) {
|
||||||
|
int next_i = i + oneUTF8CharForward(text + i, word2 - i);
|
||||||
|
w = WMWidthOfString(font, text, next_i);
|
||||||
|
if (w > width)
|
||||||
break;
|
break;
|
||||||
}
|
i = next_i;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* keep words complete if possible */
|
/* keep words complete if possible */
|
||||||
|
|||||||
@@ -122,35 +122,6 @@ static WMSelectionProcs selectionHandler = {
|
|||||||
#define TEXT_WIDTH2(tPtr, start, end) (WMWidthOfString((tPtr)->font, \
|
#define TEXT_WIDTH2(tPtr, start, end) (WMWidthOfString((tPtr)->font, \
|
||||||
&((tPtr)->text[(start)]), (end) - (start)))
|
&((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)
|
static void normalizeRange(TextField * tPtr, WMRange * range)
|
||||||
{
|
{
|
||||||
if (range->position < 0 && range->count < 0)
|
if (range->position < 0 && range->count < 0)
|
||||||
|
|||||||
@@ -132,6 +132,11 @@ static struct expert_option {
|
|||||||
|
|
||||||
{ N_("Allow windows to take focus using mouse wheel."),
|
{ N_("Allow windows to take focus using mouse wheel."),
|
||||||
/* default: */ False, OPTION_WMAKER, "MouseWheelFocus"},
|
/* default: */ False, OPTION_WMAKER, "MouseWheelFocus"},
|
||||||
|
|
||||||
|
#ifdef USE_RANDR
|
||||||
|
{ N_("Automatically (de)activate monitors on hotplug events."),
|
||||||
|
/* default: */ False, OPTION_WMAKER, "HotplugMonitor"},
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -103,9 +103,21 @@ static struct keyOption {
|
|||||||
{ "SelectKey", N_("Select active window") },
|
{ "SelectKey", N_("Select active window") },
|
||||||
{ "FocusNextKey", N_("Focus next window") },
|
{ "FocusNextKey", N_("Focus next window") },
|
||||||
{ "FocusPrevKey", N_("Focus previous 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") },
|
{ "GroupNextKey", N_("Focus next group window") },
|
||||||
{ "GroupPrevKey", N_("Focus previous 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 */
|
/* Workspace Related */
|
||||||
{ "WorkspaceMapKey", N_("Open workspace pager") },
|
{ "WorkspaceMapKey", N_("Open workspace pager") },
|
||||||
{ "NextWorkspaceKey", N_("Switch to next workspace") },
|
{ "NextWorkspaceKey", N_("Switch to next workspace") },
|
||||||
|
|||||||
+2
-2
@@ -657,11 +657,11 @@ dnl RandR support
|
|||||||
dnl =============
|
dnl =============
|
||||||
m4_divert_push([INIT_PREPARE])dnl
|
m4_divert_push([INIT_PREPARE])dnl
|
||||||
AC_ARG_ENABLE([randr],
|
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"],
|
[AS_CASE(["$enableval"],
|
||||||
[yes|no], [],
|
[yes|no], [],
|
||||||
[AC_MSG_ERROR([bad value $enableval for --enable-randr]) ]) ],
|
[AC_MSG_ERROR([bad value $enableval for --enable-randr]) ]) ],
|
||||||
[enable_randr=no])
|
[enable_randr=auto])
|
||||||
m4_divert_pop([INIT_PREPARE])dnl
|
m4_divert_pop([INIT_PREPARE])dnl
|
||||||
WM_XEXT_CHECK_XRANDR
|
WM_XEXT_CHECK_XRANDR
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ POTFILES = \
|
|||||||
$(top_srcdir)/src/framewin.c \
|
$(top_srcdir)/src/framewin.c \
|
||||||
$(top_srcdir)/src/geomview.c \
|
$(top_srcdir)/src/geomview.c \
|
||||||
$(top_srcdir)/src/icon.c \
|
$(top_srcdir)/src/icon.c \
|
||||||
|
$(top_srcdir)/src/keytree.c \
|
||||||
$(top_srcdir)/src/main.c \
|
$(top_srcdir)/src/main.c \
|
||||||
$(top_srcdir)/src/menu.c \
|
$(top_srcdir)/src/menu.c \
|
||||||
$(top_srcdir)/src/misc.c \
|
$(top_srcdir)/src/misc.c \
|
||||||
@@ -40,6 +41,7 @@ POTFILES = \
|
|||||||
$(top_srcdir)/src/pixmap.c \
|
$(top_srcdir)/src/pixmap.c \
|
||||||
$(top_srcdir)/src/placement.c \
|
$(top_srcdir)/src/placement.c \
|
||||||
$(top_srcdir)/src/properties.c \
|
$(top_srcdir)/src/properties.c \
|
||||||
|
$(top_srcdir)/src/randr.c \
|
||||||
$(top_srcdir)/src/resources.c \
|
$(top_srcdir)/src/resources.c \
|
||||||
$(top_srcdir)/src/rootmenu.c \
|
$(top_srcdir)/src/rootmenu.c \
|
||||||
$(top_srcdir)/src/screen.c \
|
$(top_srcdir)/src/screen.c \
|
||||||
|
|||||||
+3
-2
@@ -61,6 +61,8 @@ wmaker_SOURCES = \
|
|||||||
placement.h \
|
placement.h \
|
||||||
properties.c \
|
properties.c \
|
||||||
properties.h \
|
properties.h \
|
||||||
|
randr.c \
|
||||||
|
randr.h \
|
||||||
resources.c \
|
resources.c \
|
||||||
resources.h \
|
resources.h \
|
||||||
rootmenu.c \
|
rootmenu.c \
|
||||||
@@ -195,6 +197,5 @@ defaults-callbacks-dynamic:
|
|||||||
--source "$(srcdir)/defaults.c" --structure "optionList" \
|
--source "$(srcdir)/defaults.c" --structure "optionList" \
|
||||||
--field-value-ptr 4 --field-callback 5 \
|
--field-value-ptr 4 --field-callback 5 \
|
||||||
--struct-def "wPreferences=$(srcdir)/WindowMaker.h" \
|
--struct-def "wPreferences=$(srcdir)/WindowMaker.h" \
|
||||||
--struct-def "legacy_minipreview_config=$(srcdir)/defaults.c" \
|
--callback "getBool=char, getEnum=char, getInt=int, getString=char*" \
|
||||||
--callback "getBool=char, getEnum=char, getInt=int" \
|
|
||||||
--callback "getPathList=char*, getCoord=WCoord"
|
--callback "getPathList=char*, getCoord=WCoord"
|
||||||
|
|||||||
@@ -410,6 +410,9 @@ extern struct WPreferences {
|
|||||||
|
|
||||||
char dont_blink; /* do not blink icon selection */
|
char dont_blink; /* do not blink icon selection */
|
||||||
char keep_dock_on_primary_head; /* keep dock on primary head */
|
char keep_dock_on_primary_head; /* keep dock on primary head */
|
||||||
|
#ifdef USE_RANDR
|
||||||
|
char hotplug_monitor; /* auto-(de)activate monitors */
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Appearance options */
|
/* Appearance options */
|
||||||
char new_style; /* Use newstyle buttons */
|
char new_style; /* Use newstyle buttons */
|
||||||
@@ -606,6 +609,7 @@ extern struct wmaker_global_variables {
|
|||||||
|
|
||||||
Atom icon_size;
|
Atom icon_size;
|
||||||
Atom icon_tile;
|
Atom icon_tile;
|
||||||
|
Atom mark_key;
|
||||||
} wmaker;
|
} wmaker;
|
||||||
|
|
||||||
} atom;
|
} atom;
|
||||||
|
|||||||
+101
-1
@@ -4,7 +4,7 @@
|
|||||||
*
|
*
|
||||||
* Copyright (c) 1997-2003 Alfredo K. Kojima
|
* Copyright (c) 1997-2003 Alfredo K. Kojima
|
||||||
* Copyright (c) 1998-2003 Dan Pascu
|
* 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
|
* 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
|
* 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;
|
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)
|
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 the window is in another workspace, do it silently */
|
||||||
if (!netwm_hidden) {
|
if (!netwm_hidden) {
|
||||||
#ifdef USE_ANIMATIONS
|
#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;
|
wwin->flags.hidden = 0;
|
||||||
|
|
||||||
|
/* Relocate to an active head if the stored position is in dead space */
|
||||||
|
wWindowSnapToHead(wwin);
|
||||||
|
|
||||||
#ifdef USE_ANIMATIONS
|
#ifdef USE_ANIMATIONS
|
||||||
if (!wwin->screen_ptr->flags.startup && !wPreferences.no_animations && animate) {
|
if (!wwin->screen_ptr->flags.startup && !wPreferences.no_animations && animate) {
|
||||||
animateResize(wwin->screen_ptr, icon_x, icon_y,
|
animateResize(wwin->screen_ptr, icon_x, icon_y,
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
#define SAVE_GEOMETRY_ALL SAVE_GEOMETRY_X | SAVE_GEOMETRY_Y | SAVE_GEOMETRY_WIDTH | SAVE_GEOMETRY_HEIGHT
|
#define SAVE_GEOMETRY_ALL SAVE_GEOMETRY_X | SAVE_GEOMETRY_Y | SAVE_GEOMETRY_WIDTH | SAVE_GEOMETRY_HEIGHT
|
||||||
|
|
||||||
void wSetFocusTo(WScreen *scr, WWindow *wwin);
|
void wSetFocusTo(WScreen *scr, WWindow *wwin);
|
||||||
|
void wSetFocusToDirection(WScreen *scr, int direction);
|
||||||
|
|
||||||
int wMouseMoveWindow(WWindow *wwin, XEvent *ev);
|
int wMouseMoveWindow(WWindow *wwin, XEvent *ev);
|
||||||
int wKeyboardMoveResizeWindow(WWindow *wwin);
|
int wKeyboardMoveResizeWindow(WWindow *wwin);
|
||||||
|
|||||||
@@ -146,6 +146,10 @@ WAppIcon *wAppIconCreateForDock(WScreen *scr, const char *command, const char *w
|
|||||||
|
|
||||||
void create_appicon_for_application(WApplication *wapp, WWindow *wwin)
|
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 */
|
/* Try to create an icon from the dock or clip */
|
||||||
create_appicon_from_dock(wwin, wapp, wapp->main_window);
|
create_appicon_from_dock(wwin, wapp, wapp->main_window);
|
||||||
|
|
||||||
|
|||||||
+24
-1
@@ -5,7 +5,6 @@
|
|||||||
* Copyright (c) 1997-2003 Alfredo K. Kojima
|
* Copyright (c) 1997-2003 Alfredo K. Kojima
|
||||||
* Copyright (c) 1998-2003 Dan Pascu
|
* Copyright (c) 1998-2003 Dan Pascu
|
||||||
* Copyright (c) 2014-2026 Window Maker Team
|
* Copyright (c) 2014-2026 Window Maker Team
|
||||||
|
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -527,6 +526,10 @@ WDefaultEntry optionList[] = {
|
|||||||
{"KeepDockOnPrimaryHead", "NO", NULL,
|
{"KeepDockOnPrimaryHead", "NO", NULL,
|
||||||
&wPreferences.keep_dock_on_primary_head, getBool, updateDock,
|
&wPreferences.keep_dock_on_primary_head, getBool, updateDock,
|
||||||
NULL, NULL},
|
NULL, NULL},
|
||||||
|
#ifdef USE_RANDR
|
||||||
|
{"HotplugMonitor", "NO", NULL,
|
||||||
|
&wPreferences.hotplug_monitor, getBool, NULL, NULL, NULL},
|
||||||
|
#endif
|
||||||
{"HotCorners", "NO", NULL,
|
{"HotCorners", "NO", NULL,
|
||||||
&wPreferences.hot_corners, getBool, NULL, NULL, NULL},
|
&wPreferences.hot_corners, getBool, NULL, NULL, NULL},
|
||||||
{"HotCornerDelay", "250", (void *)&wPreferences.hot_corner_delay,
|
{"HotCornerDelay", "250", (void *)&wPreferences.hot_corner_delay,
|
||||||
@@ -718,6 +721,15 @@ WDefaultEntry optionList[] = {
|
|||||||
NULL, getKeybind, setKeyGrab, NULL, NULL},
|
NULL, getKeybind, setKeyGrab, NULL, NULL},
|
||||||
{"FocusPrevKey", "Mod1+Shift+Tab", (void *)WKBD_FOCUSPREV,
|
{"FocusPrevKey", "Mod1+Shift+Tab", (void *)WKBD_FOCUSPREV,
|
||||||
NULL, getKeybind, setKeyGrab, NULL, NULL},
|
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,
|
{"GroupNextKey", "None", (void *)WKBD_GROUPNEXT,
|
||||||
NULL, getKeybind, setKeyGrab, NULL, NULL},
|
NULL, getKeybind, setKeyGrab, NULL, NULL},
|
||||||
{"GroupPrevKey", "None", (void *)WKBD_GROUPPREV,
|
{"GroupPrevKey", "None", (void *)WKBD_GROUPPREV,
|
||||||
@@ -820,6 +832,17 @@ WDefaultEntry optionList[] = {
|
|||||||
NULL, getKeybind, setKeyGrab, NULL, NULL},
|
NULL, getKeybind, setKeyGrab, NULL, NULL},
|
||||||
{"PartialCaptureKey", "None", (void *)WKBD_PRINTP,
|
{"PartialCaptureKey", "None", (void *)WKBD_PRINTP,
|
||||||
NULL, getKeybind, setKeyGrab, NULL, NULL},
|
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
|
#ifdef KEEP_XKB_LOCK_STATUS
|
||||||
{"ToggleKbdModeKey", "None", (void *)WKBD_TOGGLE,
|
{"ToggleKbdModeKey", "None", (void *)WKBD_TOGGLE,
|
||||||
|
|||||||
+37
@@ -3054,6 +3054,43 @@ void wDockSwap(WDock *dock)
|
|||||||
wScreenUpdateUsableArea(scr);
|
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)
|
static pid_t execCommand(WAppIcon *btn, const char *command, WSavedState *state)
|
||||||
{
|
{
|
||||||
WScreen *scr = btn->icon->core->screen_ptr;
|
WScreen *scr = btn->icon->core->screen_ptr;
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ WAppIcon *wDockFindIconForWindow(WDock *dock, Window window);
|
|||||||
void wDockDoAutoLaunch(WDock *dock, int workspace);
|
void wDockDoAutoLaunch(WDock *dock, int workspace);
|
||||||
void wDockLaunchWithState(WAppIcon *btn, WSavedState *state);
|
void wDockLaunchWithState(WAppIcon *btn, WSavedState *state);
|
||||||
void wDockSwap(WDock *dock);
|
void wDockSwap(WDock *dock);
|
||||||
|
void wClipSnapToHead(WDock *clip);
|
||||||
|
|
||||||
#ifdef USE_DOCK_XDND
|
#ifdef USE_DOCK_XDND
|
||||||
int wDockReceiveDNDDrop(WScreen *scr, XEvent *event);
|
int wDockReceiveDNDDrop(WScreen *scr, XEvent *event);
|
||||||
|
|||||||
+167
-14
@@ -3,7 +3,7 @@
|
|||||||
* Window Maker window manager
|
* Window Maker window manager
|
||||||
*
|
*
|
||||||
* Copyright (c) 1997-2003 Alfredo K. Kojima
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_RANDR
|
#ifdef USE_RANDR
|
||||||
#include <X11/extensions/Xrandr.h>
|
#include "randr.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <X11/XKBlib.h>
|
#include <X11/XKBlib.h>
|
||||||
@@ -271,10 +271,6 @@ void DispatchEvent(XEvent * event)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ConfigureNotify:
|
case ConfigureNotify:
|
||||||
#ifdef USE_RANDR
|
|
||||||
if (event->xconfigure.window == DefaultRootWindow(dpy))
|
|
||||||
XRRUpdateConfiguration(event);
|
|
||||||
#endif
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SelectionRequest:
|
case SelectionRequest:
|
||||||
@@ -618,14 +614,14 @@ static void handleExtensions(XEvent * event)
|
|||||||
#endif /*KEEP_XKB_LOCK_STATUS */
|
#endif /*KEEP_XKB_LOCK_STATUS */
|
||||||
}
|
}
|
||||||
#ifdef USE_RANDR
|
#ifdef USE_RANDR
|
||||||
if (w_global.xext.randr.supported && event->type == (w_global.xext.randr.event_base + RRScreenChangeNotify)) {
|
if (w_global.xext.randr.supported &&
|
||||||
/* From xrandr man page: "Clients must call back into Xlib using
|
(event->type == (w_global.xext.randr.event_base + RRScreenChangeNotify) ||
|
||||||
* XRRUpdateConfiguration when screen configuration change notify
|
event->type == (w_global.xext.randr.event_base + RRNotify))) {
|
||||||
* events are generated */
|
WScreen *randr_scr;
|
||||||
XRRUpdateConfiguration(event);
|
|
||||||
WCHANGE_STATE(WSTATE_RESTARTING);
|
randr_scr = wScreenForRootWindow(event->xany.window);
|
||||||
Shutdown(WSRestartPreparationMode);
|
if (randr_scr)
|
||||||
Restart(NULL,True);
|
wRandRHandleNotify(randr_scr, event);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -1475,6 +1471,12 @@ static void wCancelChainTimer(void)
|
|||||||
#define ISMAPPED(w) ((w) && !(w)->flags.miniaturized && ((w)->flags.mapped || (w)->flags.shaded))
|
#define ISMAPPED(w) ((w) && !(w)->flags.miniaturized && ((w)->flags.mapped || (w)->flags.shaded))
|
||||||
#define ISFOCUSED(w) ((w) && (w)->flags.focused)
|
#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)
|
static void dispatchWKBDCommand(int command, WScreen *scr, WWindow *wwin, XEvent *event)
|
||||||
{
|
{
|
||||||
short widx;
|
short widx;
|
||||||
@@ -1730,6 +1732,22 @@ static void dispatchWKBDCommand(int command, WScreen *scr, WWindow *wwin, XEvent
|
|||||||
StartWindozeCycle(wwin, event, False, False);
|
StartWindozeCycle(wwin, event, False, False);
|
||||||
break;
|
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:
|
case WKBD_GROUPNEXT:
|
||||||
StartWindozeCycle(wwin, event, True, True);
|
StartWindozeCycle(wwin, event, True, True);
|
||||||
break;
|
break;
|
||||||
@@ -1979,6 +1997,29 @@ static void dispatchWKBDCommand(int command, WScreen *scr, WWindow *wwin, XEvent
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
#endif /* KEEP_XKB_LOCK_STATUS */
|
#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 */
|
/* ignore CapsLock */
|
||||||
modifiers = event->xkey.state & w_global.shortcut.modifiers_mask;
|
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 *
|
* Trie-based key-chain matching *
|
||||||
* *
|
* *
|
||||||
|
|||||||
@@ -803,6 +803,9 @@ RImage *get_rimage_icon_from_wm_hints(WIcon *icon)
|
|||||||
|
|
||||||
wwin = icon->owner;
|
wwin = icon->owner;
|
||||||
|
|
||||||
|
if (!wwin->wm_hints)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
if (!getSize(wwin->wm_hints->icon_pixmap, &w, &h, &d)) {
|
if (!getSize(wwin->wm_hints->icon_pixmap, &w, &h, &d)) {
|
||||||
icon->owner->wm_hints->flags &= ~IconPixmapHint;
|
icon->owner->wm_hints->flags &= ~IconPixmapHint;
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|||||||
@@ -77,9 +77,18 @@ enum {
|
|||||||
WKBD_WORKSPACEMAP,
|
WKBD_WORKSPACEMAP,
|
||||||
WKBD_FOCUSNEXT,
|
WKBD_FOCUSNEXT,
|
||||||
WKBD_FOCUSPREV,
|
WKBD_FOCUSPREV,
|
||||||
|
WKBD_FOCUSLEFT,
|
||||||
|
WKBD_FOCUSRIGHT,
|
||||||
|
WKBD_FOCUSUP,
|
||||||
|
WKBD_FOCUSDOWN,
|
||||||
WKBD_GROUPNEXT,
|
WKBD_GROUPNEXT,
|
||||||
WKBD_GROUPPREV,
|
WKBD_GROUPPREV,
|
||||||
WKBD_CENTRAL,
|
WKBD_CENTRAL,
|
||||||
|
WKBD_MARK_SET,
|
||||||
|
WKBD_MARK_UNSET,
|
||||||
|
WKBD_MARK_BRING,
|
||||||
|
WKBD_MARK_JUMP,
|
||||||
|
WKBD_MARK_SWAP,
|
||||||
|
|
||||||
/* window, menu */
|
/* window, menu */
|
||||||
WKBD_CLOSE,
|
WKBD_CLOSE,
|
||||||
|
|||||||
+35
-43
@@ -847,6 +847,34 @@ static char *keysymToString(KeySym keysym, unsigned int state)
|
|||||||
}
|
}
|
||||||
#endif
|
#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 *GetShortcutString(const char *shortcut)
|
||||||
{
|
{
|
||||||
char *buffer = NULL;
|
char *buffer = NULL;
|
||||||
@@ -882,50 +910,12 @@ char *GetShortcutString(const char *shortcut)
|
|||||||
char *GetShortcutKey(WShortKey key)
|
char *GetShortcutKey(WShortKey key)
|
||||||
{
|
{
|
||||||
char buf[MAX_SHORTCUT_LENGTH];
|
char buf[MAX_SHORTCUT_LENGTH];
|
||||||
char *wr, *result, *seg;
|
char *result, *seg;
|
||||||
int step;
|
int step;
|
||||||
|
|
||||||
void append_string(const char *text)
|
if (!GetCanonicalShortcutLabel(key.modifier,
|
||||||
{
|
W_KeycodeToKeysym(dpy, key.keycode, 0),
|
||||||
const char *s = text;
|
buf, sizeof(buf)))
|
||||||
|
|
||||||
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))
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* Convert the leader token to its display string */
|
/* Convert the leader token to its display string */
|
||||||
@@ -938,7 +928,9 @@ char *GetShortcutKey(WShortKey key)
|
|||||||
if (key.chain_keycodes[step] == 0)
|
if (key.chain_keycodes[step] == 0)
|
||||||
break;
|
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;
|
break;
|
||||||
|
|
||||||
seg = GetShortcutString(buf);
|
seg = GetShortcutString(buf);
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ char *ExpandOptions(WScreen * scr, const char *cmdline);
|
|||||||
void ExecuteInputCommand(WScreen *scr, const char *cmdline);
|
void ExecuteInputCommand(WScreen *scr, const char *cmdline);
|
||||||
void ExecuteExitCommand(WScreen *scr, long quickmode);
|
void ExecuteExitCommand(WScreen *scr, long quickmode);
|
||||||
char *GetShortcutString(const char *text);
|
char *GetShortcutString(const char *text);
|
||||||
|
Bool GetCanonicalShortcutLabel(unsigned int modifiers, KeySym ksym, char *buf, size_t bufsz);
|
||||||
char *GetShortcutKey(WShortKey key);
|
char *GetShortcutKey(WShortKey key);
|
||||||
char *EscapeWM_CLASS(const char *name, const char *class);
|
char *EscapeWM_CLASS(const char *name, const char *class);
|
||||||
char *StrConcatDot(const char *a, const char *b);
|
char *StrConcatDot(const char *a, const char *b);
|
||||||
|
|||||||
+9
-9
@@ -457,11 +457,10 @@ static void drawTransparentFrame(WWindow * wwin, int x, int y, int width, int he
|
|||||||
GC gc = wwin->screen_ptr->frame_gc;
|
GC gc = wwin->screen_ptr->frame_gc;
|
||||||
int h = 0;
|
int h = 0;
|
||||||
int bottom = 0;
|
int bottom = 0;
|
||||||
|
int fb = 0;
|
||||||
|
|
||||||
if (HAS_BORDER_WITH_SELECT(wwin)) {
|
if (HAS_BORDER_WITH_SELECT(wwin))
|
||||||
x += wwin->screen_ptr->frame_border_width;
|
fb = wwin->screen_ptr->frame_border_width;
|
||||||
y += wwin->screen_ptr->frame_border_width;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (HAS_TITLEBAR(wwin) && !wwin->flags.shaded) {
|
if (HAS_TITLEBAR(wwin) && !wwin->flags.shaded) {
|
||||||
h = WMFontHeight(wwin->screen_ptr->title_font) + (wPreferences.window_title_clearance +
|
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. */
|
(e.g. interactive placement), frame does not point to anything. */
|
||||||
bottom = RESIZEBAR_HEIGHT;
|
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) {
|
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) {
|
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)
|
static void draw_snap_frame(WWindow *wwin, int direction)
|
||||||
{
|
{
|
||||||
WScreen *scr;
|
WScreen *scr;
|
||||||
int head, x, y;
|
int head, x, y, fb;
|
||||||
unsigned int width, height;
|
unsigned int width, height;
|
||||||
WMRect rect;
|
WMRect rect;
|
||||||
|
|
||||||
@@ -1212,6 +1211,7 @@ static void draw_snap_frame(WWindow *wwin, int direction)
|
|||||||
y = rect.pos.y;
|
y = rect.pos.y;
|
||||||
width = rect.size.width;
|
width = rect.size.width;
|
||||||
height = rect.size.height;
|
height = rect.size.height;
|
||||||
|
fb = HAS_BORDER_WITH_SELECT(wwin) ? 2 * wwin->screen_ptr->frame_border_width : 0;
|
||||||
|
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case SNAP_LEFT:
|
case SNAP_LEFT:
|
||||||
@@ -1258,7 +1258,7 @@ static void draw_snap_frame(WWindow *wwin, int direction)
|
|||||||
break;
|
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)
|
static int get_snap_direction(WScreen *scr, int x, int y)
|
||||||
|
|||||||
+629
@@ -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
@@ -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
@@ -34,7 +34,7 @@
|
|||||||
#include <X11/Xatom.h>
|
#include <X11/Xatom.h>
|
||||||
#include <X11/XKBlib.h>
|
#include <X11/XKBlib.h>
|
||||||
#ifdef USE_RANDR
|
#ifdef USE_RANDR
|
||||||
#include <X11/extensions/Xrandr.h>
|
#include "randr.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <wraster.h>
|
#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;
|
scr->usableArea[i].y2 = scr->totalUsableArea[i].y2 = rect.pos.y + rect.size.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_RANDR
|
||||||
|
wRandRInit(scr);
|
||||||
|
#endif
|
||||||
|
|
||||||
scr->fakeGroupLeaders = WMCreateArray(16);
|
scr->fakeGroupLeaders = WMCreateArray(16);
|
||||||
|
|
||||||
CantManageScreen = 0;
|
CantManageScreen = 0;
|
||||||
@@ -669,7 +673,10 @@ WScreen *wScreenInit(int screen_number)
|
|||||||
|
|
||||||
#ifdef USE_RANDR
|
#ifdef USE_RANDR
|
||||||
if (w_global.xext.randr.supported)
|
if (w_global.xext.randr.supported)
|
||||||
XRRSelectInput(dpy, scr->root_win, RRScreenChangeNotifyMask);
|
XRRSelectInput(dpy, scr->root_win,
|
||||||
|
RRScreenChangeNotifyMask |
|
||||||
|
RRCrtcChangeNotifyMask |
|
||||||
|
RROutputChangeNotifyMask);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
XSync(dpy, False);
|
XSync(dpy, False);
|
||||||
@@ -1367,3 +1374,13 @@ void ScreenCapture(WScreen *scr, int mode)
|
|||||||
wfree(filepath);
|
wfree(filepath);
|
||||||
wfree(screenshot_dir);
|
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
@@ -59,6 +59,14 @@ typedef struct WDrawerChain {
|
|||||||
struct WDrawerChain *next;
|
struct WDrawerChain *next;
|
||||||
} WDrawerChain;
|
} 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
|
* each WScreen is saved into a context associated with its root window
|
||||||
*/
|
*/
|
||||||
@@ -299,6 +307,11 @@ typedef struct _WScreen {
|
|||||||
/* for hot-corners delay */
|
/* for hot-corners delay */
|
||||||
WMHandlerID *hot_corner_timer;
|
WMHandlerID *hot_corner_timer;
|
||||||
|
|
||||||
|
#ifdef USE_RANDR
|
||||||
|
WMHandlerID *randr_debounce_timer;
|
||||||
|
void *randr_state;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* for window shortcuts */
|
/* for window shortcuts */
|
||||||
WMArray *shortcutWindows[MAX_WINDOW_SHORTCUTS];
|
WMArray *shortcutWindows[MAX_WINDOW_SHORTCUTS];
|
||||||
|
|
||||||
@@ -327,6 +340,8 @@ typedef struct _WScreen {
|
|||||||
unsigned int jump_back_pending:1;
|
unsigned int jump_back_pending:1;
|
||||||
unsigned int ignore_focus_events:1;
|
unsigned int ignore_focus_events:1;
|
||||||
unsigned int in_hot_corner:3;
|
unsigned int in_hot_corner:3;
|
||||||
|
unsigned int mark_capture_mode:3; /* one of WMarkCaptureMode */
|
||||||
|
|
||||||
} flags;
|
} flags;
|
||||||
} WScreen;
|
} WScreen;
|
||||||
|
|
||||||
@@ -345,7 +360,7 @@ WScreen *wScreenWithNumber(int i);
|
|||||||
WScreen *wScreenForRootWindow(Window window); /* window must be valid */
|
WScreen *wScreenForRootWindow(Window window); /* window must be valid */
|
||||||
WScreen *wScreenForWindow(Window window); /* slower than above functions */
|
WScreen *wScreenForWindow(Window window); /* slower than above functions */
|
||||||
|
|
||||||
void wScreenFinish(WScreen *scr);
|
void wScreenDestroy(WScreen *scr);
|
||||||
void wScreenUpdateUsableArea(WScreen *scr);
|
void wScreenUpdateUsableArea(WScreen *scr);
|
||||||
|
|
||||||
void create_logo_image(WScreen *scr);
|
void create_logo_image(WScreen *scr);
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ static WMPropList *sMaximized;
|
|||||||
static WMPropList *sHidden;
|
static WMPropList *sHidden;
|
||||||
static WMPropList *sGeometry;
|
static WMPropList *sGeometry;
|
||||||
static WMPropList *sShortcutMask;
|
static WMPropList *sShortcutMask;
|
||||||
|
static WMPropList *sMarkKey;
|
||||||
|
|
||||||
static WMPropList *sDock;
|
static WMPropList *sDock;
|
||||||
static WMPropList *sYes, *sNo;
|
static WMPropList *sYes, *sNo;
|
||||||
@@ -118,6 +119,7 @@ static void make_keys(void)
|
|||||||
sGeometry = WMCreatePLString("Geometry");
|
sGeometry = WMCreatePLString("Geometry");
|
||||||
sDock = WMCreatePLString("Dock");
|
sDock = WMCreatePLString("Dock");
|
||||||
sShortcutMask = WMCreatePLString("ShortcutMask");
|
sShortcutMask = WMCreatePLString("ShortcutMask");
|
||||||
|
sMarkKey = WMCreatePLString("MarkKey");
|
||||||
|
|
||||||
sYes = WMCreatePLString("Yes");
|
sYes = WMCreatePLString("Yes");
|
||||||
sNo = WMCreatePLString("No");
|
sNo = WMCreatePLString("No");
|
||||||
@@ -165,6 +167,7 @@ static WMPropList *makeWindowState(WWindow * wwin, WApplication * wapp)
|
|||||||
WMPropList *win_state, *cmd, *name, *workspace;
|
WMPropList *win_state, *cmd, *name, *workspace;
|
||||||
WMPropList *shaded, *miniaturized, *maximized, *hidden, *geometry;
|
WMPropList *shaded, *miniaturized, *maximized, *hidden, *geometry;
|
||||||
WMPropList *dock, *shortcut;
|
WMPropList *dock, *shortcut;
|
||||||
|
WMPropList *mark_key_pl = NULL;
|
||||||
|
|
||||||
if (wwin->orig_main_window != None && wwin->orig_main_window != wwin->client_win)
|
if (wwin->orig_main_window != None && wwin->orig_main_window != wwin->client_win)
|
||||||
win = wwin->orig_main_window;
|
win = wwin->orig_main_window;
|
||||||
@@ -215,6 +218,9 @@ static WMPropList *makeWindowState(WWindow * wwin, WApplication * wapp)
|
|||||||
snprintf(buffer, sizeof(buffer), "%u", mask);
|
snprintf(buffer, sizeof(buffer), "%u", mask);
|
||||||
shortcut = WMCreatePLString(buffer);
|
shortcut = WMCreatePLString(buffer);
|
||||||
|
|
||||||
|
if (wwin->mark_key_label)
|
||||||
|
mark_key_pl = WMCreatePLString(wwin->mark_key_label);
|
||||||
|
|
||||||
win_state = WMCreatePLDictionary(sName, name,
|
win_state = WMCreatePLDictionary(sName, name,
|
||||||
sCommand, cmd,
|
sCommand, cmd,
|
||||||
sWorkspace, workspace,
|
sWorkspace, workspace,
|
||||||
@@ -224,6 +230,11 @@ static WMPropList *makeWindowState(WWindow * wwin, WApplication * wapp)
|
|||||||
sHidden, hidden,
|
sHidden, hidden,
|
||||||
sShortcutMask, shortcut, sGeometry, geometry, NULL);
|
sShortcutMask, shortcut, sGeometry, geometry, NULL);
|
||||||
|
|
||||||
|
if (mark_key_pl) {
|
||||||
|
WMPutInPLDictionary(win_state, sMarkKey, mark_key_pl);
|
||||||
|
WMReleasePropList(mark_key_pl);
|
||||||
|
}
|
||||||
|
|
||||||
WMReleasePropList(name);
|
WMReleasePropList(name);
|
||||||
WMReleasePropList(cmd);
|
WMReleasePropList(cmd);
|
||||||
WMReleasePropList(workspace);
|
WMReleasePropList(workspace);
|
||||||
@@ -418,6 +429,13 @@ static WSavedState *getWindowState(WScreen * scr, WMPropList * win_state)
|
|||||||
state->window_shortcuts = mask;
|
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);
|
value = WMGetFromPLDictionary(win_state, sGeometry);
|
||||||
if (value && WMIsPLString(value)) {
|
if (value && WMIsPLString(value)) {
|
||||||
if (!(sscanf(WMGetFromPLString(value), "%ix%i+%i+%i",
|
if (!(sscanf(WMGetFromPLString(value), "%ix%i+%i+%i",
|
||||||
@@ -531,6 +549,8 @@ void wSessionRestoreState(WScreen *scr)
|
|||||||
} else if ((pid = execCommand(scr, command)) > 0) {
|
} else if ((pid = execCommand(scr, command)) > 0) {
|
||||||
wWindowAddSavedState(instance, class, command, pid, state);
|
wWindowAddSavedState(instance, class, command, pid, state);
|
||||||
} else {
|
} else {
|
||||||
|
if (state->mark_key)
|
||||||
|
wfree(state->mark_key);
|
||||||
wfree(state);
|
wfree(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include "winspector.h"
|
#include "winspector.h"
|
||||||
#include "wmspec.h"
|
#include "wmspec.h"
|
||||||
#include "colormap.h"
|
#include "colormap.h"
|
||||||
|
#include "screen.h"
|
||||||
#include "shutdown.h"
|
#include "shutdown.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -81,6 +82,7 @@ void Shutdown(WShutdownMode mode)
|
|||||||
wipeDesktop(scr);
|
wipeDesktop(scr);
|
||||||
else
|
else
|
||||||
RestoreDesktop(scr);
|
RestoreDesktop(scr);
|
||||||
|
wScreenDestroy(scr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ExecExitScript();
|
ExecExitScript();
|
||||||
@@ -103,6 +105,7 @@ void Shutdown(WShutdownMode mode)
|
|||||||
kill(scr->helper_pid, SIGKILL);
|
kill(scr->helper_pid, SIGKILL);
|
||||||
wScreenSaveState(scr);
|
wScreenSaveState(scr);
|
||||||
RestoreDesktop(scr);
|
RestoreDesktop(scr);
|
||||||
|
wScreenDestroy(scr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
+19
-1
@@ -387,6 +387,7 @@ static char *atomNames[] = {
|
|||||||
"_WINDOWMAKER_COMMAND",
|
"_WINDOWMAKER_COMMAND",
|
||||||
"_WINDOWMAKER_ICON_SIZE",
|
"_WINDOWMAKER_ICON_SIZE",
|
||||||
"_WINDOWMAKER_ICON_TILE",
|
"_WINDOWMAKER_ICON_TILE",
|
||||||
|
"_WINDOWMAKER_MARK_KEY",
|
||||||
|
|
||||||
GNUSTEP_WM_ATTR_NAME,
|
GNUSTEP_WM_ATTR_NAME,
|
||||||
GNUSTEP_WM_MINIATURIZE_WINDOW,
|
GNUSTEP_WM_MINIATURIZE_WINDOW,
|
||||||
@@ -474,6 +475,7 @@ void StartUp(Bool defaultScreenOnly)
|
|||||||
w_global.atom.desktop.gtk_object_path = atom[20];
|
w_global.atom.desktop.gtk_object_path = atom[20];
|
||||||
|
|
||||||
w_global.atom.wm.ignore_focus_events = atom[21];
|
w_global.atom.wm.ignore_focus_events = atom[21];
|
||||||
|
w_global.atom.wmaker.mark_key = atom[22];
|
||||||
|
|
||||||
#ifdef USE_DOCK_XDND
|
#ifdef USE_DOCK_XDND
|
||||||
wXDNDInitializeAtoms();
|
wXDNDInitializeAtoms();
|
||||||
@@ -600,7 +602,23 @@ void StartUp(Bool defaultScreenOnly)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_RANDR
|
#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
|
#endif
|
||||||
|
|
||||||
w_global.xext.xkb.supported = XkbQueryExtension(dpy, NULL, &w_global.xext.xkb.event_base, NULL, NULL, NULL);
|
w_global.xext.xkb.supported = XkbQueryExtension(dpy, NULL, &w_global.xext.xkb.event_base, NULL, NULL, NULL);
|
||||||
|
|||||||
+28
-18
@@ -44,6 +44,8 @@
|
|||||||
((w)->wm_gnustep_attr->window_level == WMMainMenuWindowLevel || \
|
((w)->wm_gnustep_attr->window_level == WMMainMenuWindowLevel || \
|
||||||
(w)->wm_gnustep_attr->window_level == WMSubmenuWindowLevel))
|
(w)->wm_gnustep_attr->window_level == WMSubmenuWindowLevel))
|
||||||
|
|
||||||
|
#define MAX_RTEXT_LENGTH (MAX_WORKSPACENAME_WIDTH + MAX_SHORTCUT_LENGTH + 16)
|
||||||
|
|
||||||
static int initialized = 0;
|
static int initialized = 0;
|
||||||
static void observer(void *self, WMNotification * notif);
|
static void observer(void *self, WMNotification * notif);
|
||||||
static void wsobserver(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;
|
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
|
* Update switch menu
|
||||||
*/
|
*/
|
||||||
@@ -263,12 +285,8 @@ void UpdateSwitchMenu(WScreen * scr, WWindow * wwin, int action)
|
|||||||
entry->icon = switchMenuIconForWindow(scr, wwin);
|
entry->icon = switchMenuIconForWindow(scr, wwin);
|
||||||
|
|
||||||
entry->flags.indicator = 1;
|
entry->flags.indicator = 1;
|
||||||
entry->rtext = wmalloc(MAX_WORKSPACENAME_WIDTH + 8);
|
entry->rtext = wmalloc(MAX_RTEXT_LENGTH);
|
||||||
if (IS_OMNIPRESENT(wwin))
|
fillRtext(entry->rtext, MAX_RTEXT_LENGTH, wwin, scr);
|
||||||
snprintf(entry->rtext, MAX_WORKSPACENAME_WIDTH, "[*]");
|
|
||||||
else
|
|
||||||
snprintf(entry->rtext, MAX_WORKSPACENAME_WIDTH, "[%s]",
|
|
||||||
scr->workspaces[wwin->frame->workspace]->name);
|
|
||||||
|
|
||||||
if (wwin->flags.hidden) {
|
if (wwin->flags.hidden) {
|
||||||
entry->flags.indicator_type = MI_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);
|
t = ShrinkString(scr->menu_entry_font, title, MAX_WINDOWLIST_WIDTH);
|
||||||
entry->text = t;
|
entry->text = t;
|
||||||
|
|
||||||
|
fillRtext(entry->rtext, MAX_RTEXT_LENGTH, wwin, scr);
|
||||||
|
|
||||||
wMenuRealize(switchmenu);
|
wMenuRealize(switchmenu);
|
||||||
checkVisibility = 1;
|
checkVisibility = 1;
|
||||||
break;
|
break;
|
||||||
@@ -322,13 +342,7 @@ void UpdateSwitchMenu(WScreen * scr, WWindow * wwin, int action)
|
|||||||
WPixmap *ipix;
|
WPixmap *ipix;
|
||||||
int it, ion;
|
int it, ion;
|
||||||
|
|
||||||
if (IS_OMNIPRESENT(wwin)) {
|
fillRtext(entry->rtext, MAX_RTEXT_LENGTH, wwin, scr);
|
||||||
snprintf(entry->rtext, MAX_WORKSPACENAME_WIDTH, "[*]");
|
|
||||||
} else {
|
|
||||||
snprintf(entry->rtext, MAX_WORKSPACENAME_WIDTH,
|
|
||||||
"[%s]",
|
|
||||||
scr->workspaces[wwin->frame->workspace]->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
rt = entry->rtext;
|
rt = entry->rtext;
|
||||||
entry->rtext = NULL;
|
entry->rtext = NULL;
|
||||||
@@ -404,11 +418,7 @@ static void UpdateSwitchMenuWorkspace(WScreen *scr, int workspace)
|
|||||||
wwin = (WWindow *) menu->entries[i]->clientdata;
|
wwin = (WWindow *) menu->entries[i]->clientdata;
|
||||||
|
|
||||||
if (wwin->frame->workspace == workspace && !IS_OMNIPRESENT(wwin)) {
|
if (wwin->frame->workspace == workspace && !IS_OMNIPRESENT(wwin)) {
|
||||||
if (IS_OMNIPRESENT(wwin))
|
fillRtext(menu->entries[i]->rtext, MAX_RTEXT_LENGTH, wwin, scr);
|
||||||
snprintf(menu->entries[i]->rtext, MAX_WORKSPACENAME_WIDTH, "[*]");
|
|
||||||
else
|
|
||||||
snprintf(menu->entries[i]->rtext, MAX_WORKSPACENAME_WIDTH, "[%s]",
|
|
||||||
scr->workspaces[wwin->frame->workspace]->name);
|
|
||||||
menu->flags.realized = 0;
|
menu->flags.realized = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+116
-1
@@ -75,6 +75,7 @@
|
|||||||
#include "startup.h"
|
#include "startup.h"
|
||||||
#include "winmenu.h"
|
#include "winmenu.h"
|
||||||
#include "osdep.h"
|
#include "osdep.h"
|
||||||
|
#include "switchmenu.h"
|
||||||
|
|
||||||
#ifdef USE_MWM_HINTS
|
#ifdef USE_MWM_HINTS
|
||||||
# include "motif.h"
|
# 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) {
|
if (wwin->fake_group && wwin->fake_group->retainCount > 0) {
|
||||||
wwin->fake_group->retainCount--;
|
wwin->fake_group->retainCount--;
|
||||||
if (wwin->fake_group->retainCount == 0 && wwin->fake_group->leader != None) {
|
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;
|
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)
|
Bool wWindowObscuresWindow(WWindow *wwin, WWindow *obscured)
|
||||||
{
|
{
|
||||||
int w1, h1, w2, h2;
|
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);
|
wfree(wstate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* don't let transients start miniaturized if their owners are not */
|
/* 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);
|
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))
|
if (!(req_height > wwin->frame->core->height || req_width > wwin->frame->core->width))
|
||||||
XResizeWindow(dpy, wwin->client_win, req_width, req_height);
|
XResizeWindow(dpy, wwin->client_win, req_width, req_height);
|
||||||
|
|
||||||
@@ -2294,6 +2345,26 @@ void wWindowMove(WWindow *wwin, int req_x, int req_y)
|
|||||||
#endif
|
#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)
|
void wWindowUpdateButtonImages(WWindow *wwin)
|
||||||
{
|
{
|
||||||
WScreen *scr = wwin->screen_ptr;
|
WScreen *scr = wwin->screen_ptr;
|
||||||
@@ -2499,6 +2570,14 @@ void wWindowSaveState(WWindow *wwin)
|
|||||||
|
|
||||||
XChangeProperty(dpy, wwin->client_win, w_global.atom.wmaker.state,
|
XChangeProperty(dpy, wwin->client_win, w_global.atom.wmaker.state,
|
||||||
w_global.atom.wmaker.state, 32, PropModeReplace, (unsigned char *)data, 10);
|
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)
|
static int getSavedState(Window window, WSavedState ** state)
|
||||||
@@ -2508,6 +2587,7 @@ static int getSavedState(Window window, WSavedState ** state)
|
|||||||
unsigned long nitems_ret;
|
unsigned long nitems_ret;
|
||||||
unsigned long bytes_after_ret;
|
unsigned long bytes_after_ret;
|
||||||
long *data;
|
long *data;
|
||||||
|
unsigned char *mk_data = NULL;
|
||||||
|
|
||||||
if (XGetWindowProperty(dpy, window, w_global.atom.wmaker.state, 0, 10,
|
if (XGetWindowProperty(dpy, window, w_global.atom.wmaker.state, 0, 10,
|
||||||
True, w_global.atom.wmaker.state,
|
True, w_global.atom.wmaker.state,
|
||||||
@@ -2535,6 +2615,15 @@ static int getSavedState(Window window, WSavedState ** state)
|
|||||||
|
|
||||||
XFree(data);
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2861,6 +2950,9 @@ static void release_wwindowstate(WWindowState *wstate)
|
|||||||
if (wstate->command)
|
if (wstate->command)
|
||||||
wfree(wstate->command);
|
wfree(wstate->command);
|
||||||
|
|
||||||
|
if (wstate->state && wstate->state->mark_key)
|
||||||
|
wfree(wstate->state->mark_key);
|
||||||
|
|
||||||
wfree(wstate->state);
|
wfree(wstate->state);
|
||||||
wfree(wstate);
|
wfree(wstate);
|
||||||
}
|
}
|
||||||
@@ -2874,6 +2966,29 @@ void wWindowSetOmnipresent(WWindow *wwin, Bool flag)
|
|||||||
WMPostNotificationName(WMNChangedState, wwin, "omnipresent");
|
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)
|
static void resizebarMouseDown(WCoreWindow *sender, void *data, XEvent *event)
|
||||||
{
|
{
|
||||||
WWindow *wwin = data;
|
WWindow *wwin = data;
|
||||||
|
|||||||
@@ -299,6 +299,7 @@ typedef struct WWindow {
|
|||||||
int icon_w, icon_h;
|
int icon_w, icon_h;
|
||||||
RImage *net_icon_image; /* Window Image */
|
RImage *net_icon_image; /* Window Image */
|
||||||
Atom type;
|
Atom type;
|
||||||
|
char *mark_key_label; /* Vim-like Window Marking */
|
||||||
} WWindow;
|
} WWindow;
|
||||||
|
|
||||||
#define HAS_TITLEBAR(w) (!(WFLAGP((w), no_titlebar) || (w)->flags.fullscreen))
|
#define HAS_TITLEBAR(w) (!(WFLAGP((w), no_titlebar) || (w)->flags.fullscreen))
|
||||||
@@ -323,6 +324,7 @@ typedef struct WSavedState {
|
|||||||
unsigned int w;
|
unsigned int w;
|
||||||
unsigned int h;
|
unsigned int h;
|
||||||
unsigned window_shortcuts; /* mask like 1<<shortcut_number */
|
unsigned window_shortcuts; /* mask like 1<<shortcut_number */
|
||||||
|
char *mark_key; /* serialised mark key label */
|
||||||
} WSavedState;
|
} WSavedState;
|
||||||
|
|
||||||
typedef struct WWindowState {
|
typedef struct WWindowState {
|
||||||
@@ -362,6 +364,7 @@ void wWindowConfigure(WWindow *wwin, int req_x, int req_y,
|
|||||||
int req_width, int req_height);
|
int req_width, int req_height);
|
||||||
|
|
||||||
void wWindowMove(WWindow *wwin, int req_x, int req_y);
|
void wWindowMove(WWindow *wwin, int req_x, int req_y);
|
||||||
|
void wWindowSnapToHead(WWindow *wwin);
|
||||||
|
|
||||||
void wWindowSynthConfigureNotify(WWindow *wwin);
|
void wWindowSynthConfigureNotify(WWindow *wwin);
|
||||||
|
|
||||||
@@ -402,10 +405,15 @@ WMagicNumber wWindowGetSavedState(Window win);
|
|||||||
|
|
||||||
void wWindowDeleteSavedState(WMagicNumber id);
|
void wWindowDeleteSavedState(WMagicNumber id);
|
||||||
|
|
||||||
|
Bool wWindowIsFullyCovered(WWindow *wwin);
|
||||||
Bool wWindowObscuresWindow(WWindow *wwin, WWindow *obscured);
|
Bool wWindowObscuresWindow(WWindow *wwin, WWindow *obscured);
|
||||||
|
|
||||||
void wWindowSetOmnipresent(WWindow *wwin, Bool flag);
|
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
|
#ifdef XKB_BUTTON_HINT
|
||||||
void wWindowGetLanguageLabel(int group_index, char *label);
|
void wWindowGetLanguageLabel(int group_index, char *label);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+3
-3
@@ -1443,10 +1443,10 @@ static void create_tab_app_specific(WWindow *wwin, InspectorPanel *panel, int fr
|
|||||||
|
|
||||||
if (WFLAGP(wwin, emulate_appicon)) {
|
if (WFLAGP(wwin, emulate_appicon)) {
|
||||||
WMSetButtonEnabled(panel->appChk[1], False);
|
WMSetButtonEnabled(panel->appChk[1], False);
|
||||||
WMSetButtonEnabled(panel->moreChk[7], True);
|
WMSetButtonEnabled(panel->moreChk[8], True);
|
||||||
} else {
|
} else {
|
||||||
WMSetButtonEnabled(panel->appChk[1], True);
|
WMSetButtonEnabled(panel->appChk[1], True);
|
||||||
WMSetButtonEnabled(panel->moreChk[7], False);
|
WMSetButtonEnabled(panel->moreChk[8], False);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ((wwin->transient_for != None && wwin->transient_for != scr->root_win)
|
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
|
else
|
||||||
tmp = True;
|
tmp = True;
|
||||||
|
|
||||||
WMSetButtonEnabled(panel->moreChk[7], tmp);
|
WMSetButtonEnabled(panel->moreChk[8], tmp);
|
||||||
|
|
||||||
WMSetPopUpButtonItemEnabled(panel->pagePopUp, 4, False);
|
WMSetPopUpButtonItemEnabled(panel->pagePopUp, 4, False);
|
||||||
panel->appFrm = NULL;
|
panel->appFrm = NULL;
|
||||||
|
|||||||
+2
-2
@@ -1524,7 +1524,7 @@ void wNETWMCheckClientHints(WWindow *wwin, int *layer, int *workspace)
|
|||||||
*workspace = desktop;
|
*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,
|
XA_ATOM, &type_ret, &fmt_ret, &nitems_ret,
|
||||||
&bytes_after_ret, (unsigned char **)&data) == Success && data) {
|
&bytes_after_ret, (unsigned char **)&data) == Success && data) {
|
||||||
|
|
||||||
@@ -1535,7 +1535,7 @@ void wNETWMCheckClientHints(WWindow *wwin, int *layer, int *workspace)
|
|||||||
XFree(data);
|
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,
|
XA_ATOM, &type_ret, &fmt_ret, &nitems_ret,
|
||||||
&bytes_after_ret, (unsigned char **)&data) == Success && data) {
|
&bytes_after_ret, (unsigned char **)&data) == Success && data) {
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -4,10 +4,16 @@ AUTOMAKE_OPTIONS = no-dependencies
|
|||||||
|
|
||||||
EXTRA_DIST = notest.c
|
EXTRA_DIST = notest.c
|
||||||
|
|
||||||
noinst_PROGRAMS = wtest
|
noinst_PROGRAMS = wtest wm_fsm_test
|
||||||
|
|
||||||
wtest_SOURCES = wtest.c
|
wtest_SOURCES = wtest.c
|
||||||
|
|
||||||
wtest_LDADD = $(top_builddir)/wmlib/libWMaker.la @XLFLAGS@ @XLIBS@
|
wtest_LDADD = $(top_builddir)/wmlib/libWMaker.la @XLFLAGS@ @XLIBS@
|
||||||
|
|
||||||
AM_CPPFLAGS = -g -D_BSD_SOURCE @XCFLAGS@ -I$(top_srcdir)/wmlib
|
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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user