Repository: qtile/qtile

Issue:
Qtile Drag to Float using mod+mouse1 having issues
There seems to be some really weird issue:  when I use mod + mouse1 to float a window , if I release mod first then mouse1, qtile behaves as if the mod key is still pressed which means if I click on mouse1 again, the window also moves. I am on the latest qtile git
Also happens on the default config
```
qtile --version
0.17.1.dev230+gd9f7b281
```
Feel free to ask any logs
Edit : some corrections


Discussion:
Bisected to 373ad5531e7b48ed28c650e7a895f661e98ce9a5

@m-col 
Thanks @drishal and @ramnes. Looks like old behaviour was the let the drag continue after releasing the modifier until the button was released, which was an innocuous side effect of us not listening to key releases. We have a couple options:

1. Listen to key release events and stop the drag when the modifiers no longer match the configured `Drag`.
2. Undo my change which made the keys of `qtile.mouse_map` be `(button.button_code, modmask)`, then upon button release get any affected `Drag`s with `qtile.mouse_map[button_code]` and disable them all, irrespective of their modifiers. This is the old behaviour.

What do people think is preferable?
I think it's preferable to allow the drag without the modifier being pressed the whole time.

Resolving patch:
diff --git a/libqtile/config.py b/libqtile/config.py
--- a/libqtile/config.py
+++ b/libqtile/config.py
@@ -110,6 +110,7 @@ def __init__(self, modifiers: List[str], button: str, *commands, **kwargs):
         self.button_code = int(self.button.replace('Button', ''))
         for k, v in kwargs.items():
             setattr(self, k, v)
+        self.modmask: int = 0
 
 
 class Drag(Mouse):
diff --git a/libqtile/core/manager.py b/libqtile/core/manager.py
--- a/libqtile/core/manager.py
+++ b/libqtile/core/manager.py
@@ -68,7 +68,7 @@ def __init__(
         self.socket_path = socket_path
 
         self._drag: Optional[Tuple] = None
-        self.mouse_map: Dict[Tuple[int, int], List[Union[Click, Drag]]] = {}
+        self.mouse_map: Dict[int, List[Union[Click, Drag]]] = {}
         self.mouse_position = (0, 0)
 
         self.windows_map: Dict[int, base.WindowType] = {}
@@ -408,14 +408,13 @@ def cmd_ungrab_all_chords(self) -> None:
     def grab_button(self, button: Union[Click, Drag]) -> None:
         """Grab the given mouse button event"""
         try:
-            modmask = self.core.grab_button(button)
+            button.modmask = self.core.grab_button(button)
         except utils.QtileError:
             logger.warning(f"Unknown modifier(s): {button.modifiers}")
             return
-        key = (button.button_code, modmask)
-        if key not in self.mouse_map:
-            self.mouse_map[key] = []
-        self.mouse_map[key].append(button)
+        if button.button_code not in self.mouse_map:
+            self.mouse_map[button.button_code] = []
+        self.mouse_map[button.button_code].append(button)
 
     def update_desktops(self) -> None:
         try:
@@ -626,7 +625,10 @@ def _find_closest_closest(self, x, y, candidate_screens):
 
     def process_button_click(self, button_code, modmask, x, y, event) -> None:
         self.mouse_position = (x, y)
-        for m in self.mouse_map.get((button_code, modmask), []):
+        for m in self.mouse_map.get(button_code, []):
+            if not m.modmask == modmask:
+                continue
+
             if m.focus == "before":
                 self.core.focus_by_click(event)
 
@@ -658,15 +660,11 @@ def process_button_click(self, button_code, modmask, x, y, event) -> None:
                 self.core.focus_by_click(event)
 
     def process_button_release(self, button_code, modmask):
-        for m in self.mouse_map.get((button_code, modmask), []):
-            if not m:
-                logger.info(
-                    "Ignoring unknown button release: %s" % button_code
-                )
-                continue
+        for m in self.mouse_map.get(button_code, []):
             if isinstance(m, Drag):
                 self._drag = None
                 self.core.ungrab_pointer()
+                return
 
     def process_button_motion(self, x, y):
         self.mouse_position = (x, y)


Test patch: