Skip to content

Commit

Permalink
feat: move windows between monitors with mouse
Browse files Browse the repository at this point in the history
  • Loading branch information
atennert committed Aug 2, 2021
1 parent b59c1f6 commit 81d268f
Show file tree
Hide file tree
Showing 21 changed files with 307 additions and 37 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* adjust ordering of monitors by y and x values
* include alternative actions window-move-next and window-move-previous
* set cursor on root window
* add moving of windows with mouse

21.2
* fix window toggling (forward)
Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Thank you very much to the creators of the following resources:
* Win / Lin / Super
* Meta
* Hyper
* By clicking and holding a window title bar with the left mouse button and dragging the window can be moved between monitors
* General settings
* title: title that is displayed in the top bar in normal and maximized mode
* title-image: optional, path to image in XPM format, if set, it will be shown instead of the title. Should have a height of 40px and be at least 16px shorter then the normal modes' top bar.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.atennert.lcarswm.events

import de.atennert.lcarswm.log.Logger
import de.atennert.lcarswm.mouse.MoveWindowManager
import de.atennert.lcarswm.system.api.InputApi
import de.atennert.lcarswm.window.WindowFocusHandler
import de.atennert.lcarswm.window.WindowList
Expand All @@ -12,20 +13,29 @@ class ButtonPressHandler(
private val logger: Logger,
private val inputApi: InputApi,
private val windowList: WindowList,
private val focusHandler: WindowFocusHandler
private val focusHandler: WindowFocusHandler,
private val moveWindowManager: MoveWindowManager
) : XEventHandler {
override val xEventType = ButtonPress

override fun handleEvent(event: XEvent): Boolean {
val windowId = event.xbutton.window
val subWindowId = event.xbutton.subwindow
logger.logDebug("ButtonPressHandler::handleEvent::windowId: $windowId, sub window: $subWindowId")
logger.logDebug("ButtonPressHandler::handleEvent::windowId: $windowId, sub window: $subWindowId, x y: ${event.xbutton.x} ${event.xbutton.y} (${event.xbutton.x_root} ${event.xbutton.y_root})")

val window = windowList.getByFrame(windowId) ?: return false
windowList.getByAny(windowId)?.let { window ->
logger.logDebug("handle focus")
focusHandler.setFocusedWindow(window.id)

focusHandler.setFocusedWindow(window.id)
if (windowId == window.titleBar) {
moveWindowManager.press(window, event.xbutton.x_root, event.xbutton.y_root)
}
}

inputApi.allowEvents(ReplayPointer, event.xbutton.time)
windowList.get(windowId)?.let {
logger.logDebug("replay event")
inputApi.allowEvents(ReplayPointer, event.xbutton.time)
}
return false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package de.atennert.lcarswm.events

import de.atennert.lcarswm.log.Logger
import de.atennert.lcarswm.mouse.MoveWindowManager
import de.atennert.lcarswm.system.api.InputApi
import de.atennert.lcarswm.window.WindowFocusHandler
import de.atennert.lcarswm.window.WindowList
import xlib.ButtonRelease
import xlib.XEvent

class ButtonReleaseHandler(
private val logger: Logger,
private val moveWindowManager: MoveWindowManager
) : XEventHandler {
override val xEventType = ButtonRelease

override fun handleEvent(event: XEvent): Boolean {
val windowId = event.xbutton.window
val subWindowId = event.xbutton.subwindow
logger.logDebug("ButtonReleaseHandler::handleEvent::windowId: $windowId, sub window: $subWindowId, x y: ${event.xbutton.x} ${event.xbutton.y} (${event.xbutton.x_root} ${event.xbutton.y_root})")

moveWindowManager.release()
return false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package de.atennert.lcarswm.events

import de.atennert.lcarswm.log.Logger
import de.atennert.lcarswm.mouse.MoveWindowManager
import de.atennert.lcarswm.system.api.InputApi
import de.atennert.lcarswm.window.WindowFocusHandler
import de.atennert.lcarswm.window.WindowList
import xlib.*

class MotionNotifyHandler(
private val logger: Logger,
private val moveWindowManager: MoveWindowManager
) : XEventHandler {
override val xEventType = MotionNotify

override fun handleEvent(event: XEvent): Boolean {
val windowId = event.xmotion.window
val subWindowId = event.xmotion.subwindow
logger.logDebug("MotionNotifyHandler::handleEvent::windowId: $windowId, sub window: $subWindowId, x y: ${event.xmotion.x} ${event.xmotion.y} (${event.xmotion.x_root} ${event.xmotion.y_root})")

moveWindowManager.move(event.xmotion.x_root, event.xmotion.y_root)
return false
}
}
22 changes: 22 additions & 0 deletions src/nativeMain/kotlin/de/atennert/lcarswm/keys/KeyManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,28 @@ class KeyManager(private val inputApi: InputApi) {
}
}

fun grabButton(button: UInt, modifiers: Int, windowId: Window, mask: UInt, pointerMode: Int, cursor: Cursor) {
lockMasks.forEach { lockMask ->
inputApi.grabButton(
button,
(modifiers or lockMask).convert(),
windowId,
true,
mask,
pointerMode,
GrabModeAsync,
None.convert(),
cursor
)
}
}

fun ungrabButton(button: UInt, modifiers: Int, windowId: Window) {
lockMasks.forEach { lockMask ->
inputApi.ungrabButton(button, (modifiers or lockMask).convert(), windowId)
}
}

/**
* @return key sym for a given key code
*/
Expand Down
13 changes: 11 additions & 2 deletions src/nativeMain/kotlin/de/atennert/lcarswm/lifecycle/startup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import de.atennert.lcarswm.keys.KeySessionManager
import de.atennert.lcarswm.log.Logger
import de.atennert.lcarswm.monitor.MonitorManager
import de.atennert.lcarswm.monitor.MonitorManagerImpl
import de.atennert.lcarswm.mouse.MoveWindowManager
import de.atennert.lcarswm.settings.SettingsReader
import de.atennert.lcarswm.signal.Signal
import de.atennert.lcarswm.signal.SignalHandler
Expand Down Expand Up @@ -181,9 +182,12 @@ fun startup(system: SystemApi, logger: Logger): RuntimeResources? {
windowNameReader,
appMenuHandler,
statusBarHandler,
windowList
windowList,
keyManager
)

val moveWindowManager = MoveWindowManager(logger, windowCoordinator)

focusHandler.registerObserver(
FocusSessionKeyboardGrabber(system, eventTime, rootWindowPropertyHandler.ewmhSupportWindow))

Expand All @@ -207,6 +211,7 @@ fun startup(system: SystemApi, logger: Logger): RuntimeResources? {

monitorManager.registerObserver(appMenuHandler)
monitorManager.registerObserver(statusBarHandler)
monitorManager.registerObserver(moveWindowManager)

windowList.registerObserver(appMenuHandler.windowListObserver)

Expand All @@ -220,6 +225,7 @@ fun startup(system: SystemApi, logger: Logger): RuntimeResources? {
windowCoordinator,
focusHandler,
keyManager,
moveWindowManager,
toggleSessionManager,
uiDrawer,
atomLibrary,
Expand Down Expand Up @@ -341,6 +347,7 @@ private fun createEventManager(
windowCoordinator: WindowCoordinator,
focusHandler: WindowFocusHandler,
keyManager: KeyManager,
moveWindowManager: MoveWindowManager,
toggleSessionManager: KeySessionManager,
uiDrawer: UIDrawing,
atomLibrary: AtomLibrary,
Expand All @@ -357,10 +364,12 @@ private fun createEventManager(
return EventDistributor.Builder(logger)
.addEventHandler(ConfigureRequestHandler(system, logger, windowRegistration, windowCoordinator, appMenuHandler, statusBarHandler))
.addEventHandler(DestroyNotifyHandler(logger, windowRegistration, appMenuHandler, statusBarHandler))
.addEventHandler(ButtonPressHandler(logger, system, windowList, focusHandler))
.addEventHandler(ButtonPressHandler(logger, system, windowList, focusHandler, moveWindowManager))
.addEventHandler(ButtonReleaseHandler(logger, moveWindowManager))
.addEventHandler(KeyPressHandler(logger, keyManager, keyConfiguration, toggleSessionManager, monitorManager, windowCoordinator, focusHandler, uiDrawer))
.addEventHandler(KeyReleaseHandler(logger, system, focusHandler, keyManager, keyConfiguration, toggleSessionManager, atomLibrary))
.addEventHandler(MapRequestHandler(logger, windowRegistration))
.addEventHandler(MotionNotifyHandler(logger, moveWindowManager))
.addEventHandler(UnmapNotifyHandler(logger, windowRegistration, uiDrawer))
.addEventHandler(screenChangeHandler)
.addEventHandler(ReparentNotifyHandler(logger, windowRegistration))
Expand Down
8 changes: 8 additions & 0 deletions src/nativeMain/kotlin/de/atennert/lcarswm/monitor/Monitor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ data class Monitor(
return other.x == this.x && other.y == this.y
}

/**
* Check if a pixel is shown on this monitor.
* @return <code>true</code> if the pixel is on the monitor, <code>false</code> otherwise
*/
fun isOnMonitor(x: Int, y: Int): Boolean {
return this.x <= x && x < (this.x + this.width) && this.y <= y && y < (this.y + this.height)
}

/**
* @return the current window measurements in the form [x, y, width, height], depending on the current screenMode
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class MonitorManagerImpl(private val randrApi: RandrApi, private val rootWindowI

fun registerObserver(observer: MonitorObserver) {
observers.add(observer)
observer.updateMonitors(monitors)
}

override fun updateMonitorList() {
Expand All @@ -40,7 +41,7 @@ class MonitorManagerImpl(private val randrApi: RandrApi, private val rootWindowI
.map { (monitor, outputInfo) -> addMeasurementToMonitor(monitor, outputInfo!!.pointed.crtc, monitorData) }
.sortedBy { (it.y + it.height).toULong().shl(32) + it.x.toULong() }

observers.forEach { it.updateMonitors() }
observers.forEach { it.updateMonitors(monitors) }
}

private fun getMonitorData(): CPointer<XRRScreenResources> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import de.atennert.lcarswm.ScreenMode
interface MonitorObserver {
fun toggleScreenMode(newScreenMode: ScreenMode)

fun updateMonitors()
fun updateMonitors(monitors: List<Monitor>)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package de.atennert.lcarswm.mouse

import de.atennert.lcarswm.ScreenMode
import de.atennert.lcarswm.log.Logger
import de.atennert.lcarswm.monitor.Monitor
import de.atennert.lcarswm.monitor.MonitorObserver
import de.atennert.lcarswm.window.FramedWindow
import de.atennert.lcarswm.window.WindowCoordinator

class MoveWindowManager(
private val logger: Logger,
private val windowCoordinator: WindowCoordinator
) : MonitorObserver {

private var monitors = emptyList<Monitor>()

private lateinit var lastWindowMonitor: Monitor

private var targetWindow: FramedWindow? = null

fun press(window: FramedWindow, x: Int, y: Int) {
targetWindow?.let {
logger.logWarning("MoveWindowManager::press::apparently there was a window moving in process for ${it.id}.")
}

targetWindow = window
lastWindowMonitor = getMonitor(x, y) ?: return
}

fun move(x: Int, y: Int) {
targetWindow?.let {
val newMonitor = getMonitor(x, y) ?: return
if (newMonitor.id != lastWindowMonitor.id) {
windowCoordinator.moveWindowToMonitor(it.id, newMonitor)
lastWindowMonitor = newMonitor
}
}
}

fun release() {
targetWindow = null
}

private fun getMonitor(x: Int, y: Int): Monitor? {
try {
return monitors.first { it.isOnMonitor(x, y) }
} catch (e: Exception) {
logger.logError(e.toString())
return null
}
}

override fun toggleScreenMode(newScreenMode: ScreenMode) {}

override fun updateMonitors(monitors: List<Monitor>) {
this.monitors = monitors
logger.logDebug("MoveWindowManager::updateMonitors::$monitors")

// cancel moving the window
targetWindow?.let {
logger.logWarning("MoveWindowManager::updateMonitors::cancel moving of ${it.id}")
}
targetWindow = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,17 @@ class ActiveWindowCoordinator(
frameDrawer.drawFrame(window, nextMonitor)
}

override fun moveWindowToMonitor(windowId: Window, monitor: Monitor) {
val window = windowsOnMonitors.keys.single { it.id == windowId }
windowsOnMonitors[window] = monitor
adjustWindowPositionAndSize(
eventApi,
monitor.getWindowMeasurements(),
window
)
frameDrawer.drawFrame(window, monitor)
}

override fun getMonitorForWindow(windowId: Window): Monitor {
return windowsOnMonitors.entries.single { (window, _) -> window.id == windowId }.value
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import de.atennert.lcarswm.atom.Atoms
import de.atennert.lcarswm.conversion.combine
import de.atennert.lcarswm.conversion.toUByteArray
import de.atennert.lcarswm.events.sendConfigureNotify
import de.atennert.lcarswm.monitor.Monitor
import de.atennert.lcarswm.monitor.MonitorManager
import de.atennert.lcarswm.monitor.MonitorObserver
import de.atennert.lcarswm.system.MessageQueue
Expand Down Expand Up @@ -79,7 +80,7 @@ class AppMenuHandler(
systemApi.unmapWindow(window.frame)
}

override fun updateMonitors() {
override fun updateMonitors(monitors: List<Monitor>) {
window?.let { window ->
resizeWindow(window)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import de.atennert.lcarswm.atom.Atoms
import de.atennert.lcarswm.conversion.combine
import de.atennert.lcarswm.conversion.toUByteArray
import de.atennert.lcarswm.events.sendConfigureNotify
import de.atennert.lcarswm.monitor.Monitor
import de.atennert.lcarswm.monitor.MonitorManager
import de.atennert.lcarswm.monitor.MonitorObserver
import de.atennert.lcarswm.system.api.SystemApi
Expand Down Expand Up @@ -74,7 +75,7 @@ class StatusBarHandler(
systemApi.unmapWindow(window.frame)
}

override fun updateMonitors() {
override fun updateMonitors(monitors: List<Monitor>) {
window?.let { window ->
resizeWindow(window)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ interface WindowCoordinator {

fun moveWindowToPreviousMonitor(windowId: Window)

fun moveWindowToMonitor(windowId: Window, monitor: Monitor)

fun getMonitorForWindow(windowId: Window): Monitor

fun getWindowMeasurements(windowId: Window): WindowMeasurements
Expand Down
Loading

0 comments on commit 81d268f

Please sign in to comment.