We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Shouldn't call methods directly on an @action_class
You shouldn't call methods directly on an @action_class, for example:

https://github.com/knausj85/knausj_talon/blob/65ccd94b8fe97056a34b858b2f1f261cc5269c3f/mouse_grid/mouse_grid.py#L282

This will continue to work, but generate a warning in the next beta.

You should probably use `actions.self.grid_narrow(...)` instead.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mouse_grid/mouse_grid.py`
Content:
```
1 # courtesy of https://github.com/timo/
2 # see https://github.com/timo/talon_scripts
3 from talon import Module, Context, app, canvas, screen, settings, ui, ctrl, cron
4 from talon.skia import Shader, Color, Paint, Rect
5 from talon.types.point import Point2d
6 from talon_plugins import eye_mouse, eye_zoom_mouse
7 from typing import Union
8 
9 import math, time
10 
11 import typing
12 
13 mod = Module()
14 narrow_expansion = mod.setting(
15     "grid_narrow_expansion",
16     type=int,
17     default=0,
18     desc="""After narrowing, grow the new region by this many pixels in every direction, to make things immediately on edges easier to hit, and when the grid is at its smallest, it allows you to still nudge it around""",
19 )
20 
21 mod.tag("mouse_grid_showing", desc="Tag indicates whether the mouse grid is showing")
22 mod.tag("mouse_grid_enabled", desc="Deprecated: do not use.  Activates legacy m grid command")
23 ctx = Context()
24 
25 
26 class MouseSnapNine:
27     def __init__(self):
28         self.screen = None
29         self.rect = None
30         self.history = []
31         self.img = None
32         self.mcanvas = None
33         self.active = False
34         self.count = 0
35         self.was_control_mouse_active = False
36         self.was_zoom_mouse_active = False
37 
38     def setup(self, *, rect: Rect = None, screen_num: int = None):
39         screens = ui.screens()
40         # each if block here might set the rect to None to indicate failure
41         if rect is not None:
42             try:
43                 screen = ui.screen_containing(*rect.center)
44             except Exception:
45                 rect = None
46         if rect is None and screen_num is not None:
47             screen = screens[screen_num % len(screens)]
48             rect = screen.rect
49         if rect is None:
50             screen = screens[0]
51             rect = screen.rect
52         self.rect = rect.copy()
53         self.screen = screen
54         self.count = 0
55         self.img = None
56         if self.mcanvas is not None:
57             self.mcanvas.close()
58         self.mcanvas = canvas.Canvas.from_screen(screen)
59         if self.active:
60             self.mcanvas.register("draw", self.draw)
61             self.mcanvas.freeze()
62 
63     def show(self):
64         if self.active:
65             return
66         # noinspection PyUnresolvedReferences
67         if eye_zoom_mouse.zoom_mouse.enabled:
68             self.was_zoom_mouse_active = True
69             eye_zoom_mouse.toggle_zoom_mouse(False)
70         if eye_mouse.control_mouse.enabled:
71             self.was_control_mouse_active = True
72             eye_mouse.control_mouse.toggle()
73         self.mcanvas.register("draw", self.draw)
74         self.mcanvas.freeze()
75         self.active = True
76         return
77 
78     def close(self):
79         if not self.active:
80             return
81         self.mcanvas.unregister("draw", self.draw)
82         self.mcanvas.close()
83         self.mcanvas = None
84         self.img = None
85 
86         self.active = False
87         if self.was_control_mouse_active and not eye_mouse.control_mouse.enabled:
88             eye_mouse.control_mouse.toggle()
89         if self.was_zoom_mouse_active and not eye_zoom_mouse.zoom_mouse.enabled:
90             eye_zoom_mouse.toggle_zoom_mouse(True)
91 
92         self.was_zoom_mouse_active = False
93         self.was_control_mouse_active = False
94 
95     def draw(self, canvas):
96         paint = canvas.paint
97 
98         def draw_grid(offset_x, offset_y, width, height):
99             canvas.draw_line(
100                 offset_x + width // 3,
101                 offset_y,
102                 offset_x + width // 3,
103                 offset_y + height,
104             )
105             canvas.draw_line(
106                 offset_x + 2 * width // 3,
107                 offset_y,
108                 offset_x + 2 * width // 3,
109                 offset_y + height,
110             )
111 
112             canvas.draw_line(
113                 offset_x,
114                 offset_y + height // 3,
115                 offset_x + width,
116                 offset_y + height // 3,
117             )
118             canvas.draw_line(
119                 offset_x,
120                 offset_y + 2 * height // 3,
121                 offset_x + width,
122                 offset_y + 2 * height // 3,
123             )
124 
125         def draw_crosses(offset_x, offset_y, width, height):
126             for row in range(0, 2):
127                 for col in range(0, 2):
128                     cx = offset_x + width / 6 + (col + 0.5) * width / 3
129                     cy = offset_y + height / 6 + (row + 0.5) * height / 3
130 
131                     canvas.draw_line(cx - 10, cy, cx + 10, cy)
132                     canvas.draw_line(cx, cy - 10, cx, cy + 10)
133 
134         grid_stroke = 1
135 
136         def draw_text(offset_x, offset_y, width, height):
137             canvas.paint.text_align = canvas.paint.TextAlign.CENTER
138             for row in range(3):
139                 for col in range(3):
140                     text_string = ""
141                     if settings["user.grids_put_one_bottom_left"]:
142                         text_string = f"{(2 - row)*3+col+1}"
143                     else:
144                         text_string = f"{row*3+col+1}"
145                     text_rect = canvas.paint.measure_text(text_string)[1]
146                     background_rect = text_rect.copy()
147                     background_rect.center = Point2d(
148                         offset_x + width / 6 + col * width / 3,
149                         offset_y + height / 6 + row * height / 3,
150                     )
151                     background_rect = background_rect.inset(-4)
152                     paint.color = "9999995f"
153                     paint.style = Paint.Style.FILL
154                     canvas.draw_rect(background_rect)
155                     paint.color = "00ff00ff"
156                     canvas.draw_text(
157                         text_string,
158                         offset_x + width / 6 + col * width / 3,
159                         offset_y + height / 6 + row * height / 3 + text_rect.height / 2,
160                     )
161 
162         if self.count < 2:
163             paint.color = "00ff007f"
164             for which in range(1, 10):
165                 gap = 35 - self.count * 10
166                 if not self.active:
167                     gap = 45
168                 draw_crosses(*self.calc_narrow(which, self.rect))
169 
170         paint.stroke_width = grid_stroke
171         if self.active:
172             paint.color = "ff0000ff"
173         else:
174             paint.color = "000000ff"
175         if self.count >= 2:
176             aspect = self.rect.width / self.rect.height
177             if aspect >= 1:
178                 w = self.screen.width / 3
179                 h = w / aspect
180             else:
181                 h = self.screen.height / 3
182                 w = h * aspect
183             x = self.screen.x + (self.screen.width - w) / 2
184             y = self.screen.y + (self.screen.height - h) / 2
185             self.draw_zoom(canvas, x, y, w, h)
186             draw_grid(x, y, w, h)
187             draw_text(x, y, w, h)
188         else:
189             draw_grid(self.rect.x, self.rect.y, self.rect.width, self.rect.height)
190 
191             paint.textsize += 12 - self.count * 3
192             draw_text(self.rect.x, self.rect.y, self.rect.width, self.rect.height)
193 
194     def calc_narrow(self, which, rect):
195         rect = rect.copy()
196         bdr = narrow_expansion.get()
197         row = int(which - 1) // 3
198         col = int(which - 1) % 3
199         if settings["user.grids_put_one_bottom_left"]:
200             row = 2 - row
201         rect.x += int(col * rect.width // 3) - bdr
202         rect.y += int(row * rect.height // 3) - bdr
203         rect.width = (rect.width // 3) + bdr * 2
204         rect.height = (rect.height // 3) + bdr * 2
205         return rect
206 
207     def narrow(self, which, move=True):
208         if which < 1 or which > 9:
209             return
210         self.save_state()
211         rect = self.calc_narrow(which, self.rect)
212         # check count so we don't bother zooming in _too_ far
213         if self.count < 5:
214             self.rect = rect.copy()
215             self.count += 1
216         if move:
217             ctrl.mouse_move(*rect.center)
218         if self.count >= 2:
219             self.update_screenshot()
220         else:
221             self.mcanvas.freeze()
222 
223     def update_screenshot(self):
224         def finish_capture():
225             self.img = screen.capture_rect(self.rect)
226             self.mcanvas.freeze()
227 
228         self.mcanvas.hide()
229         cron.after("16ms", finish_capture)
230 
231     def draw_zoom(self, canvas, x, y, w, h):
232         if self.img:
233             src = Rect(0, 0, self.img.width, self.img.height)
234             dst = Rect(x, y, w, h)
235             canvas.draw_image_rect(self.img, src, dst)
236 
237     def narrow_to_pos(self, x, y):
238         col_size = int(self.width // 3)
239         row_size = int(self.height // 3)
240         col = math.floor((x - self.rect.x) / col_size)
241         row = math.floor((y - self.rect.x) / row_size)
242         self.narrow(1 + col + 3 * row, move=False)
243 
244     def save_state(self):
245         self.history.append((self.count, self.rect.copy()))
246 
247     def go_back(self):
248         # FIXME: need window and screen tracking
249         self.count, self.rect = self.history.pop()
250         self.mcanvas.freeze()
251 
252 
253 mg = MouseSnapNine()
254 
255 
256 @mod.action_class
257 class GridActions:
258     def grid_activate():
259         """Show mouse grid"""
260         if not mg.mcanvas:
261             mg.setup()
262         mg.show()
263         ctx.tags = ["user.mouse_grid_showing"]
264 
265     def grid_place_window():
266         """Places the grid on the currently active window"""
267         mg.setup(rect=ui.active_window().rect)
268 
269     def grid_reset():
270         """Resets the grid to fill the whole screen again"""
271         if mg.active:
272             mg.setup()
273 
274     def grid_select_screen(screen: int):
275         """Brings up mouse grid"""
276         mg.setup(screen_num=screen - 1)
277         mg.show()
278 
279     def grid_narrow_list(digit_list: typing.List[str]):
280         """Choose fields multiple times in a row"""
281         for d in digit_list:
282             GridActions.grid_narrow(int(d))
283 
284     def grid_narrow(digit: Union[int, str]):
285         """Choose a field of the grid and narrow the selection down"""
286         mg.narrow(int(digit))
287 
288     def grid_go_back():
289         """Sets the grid state back to what it was before the last command"""
290         mg.go_back()
291 
292     def grid_close():
293         """Close the active grid"""
294         ctx.tags = []
295         mg.close()
296
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mouse_grid/mouse_grid.py b/mouse_grid/mouse_grid.py
--- a/mouse_grid/mouse_grid.py
+++ b/mouse_grid/mouse_grid.py
@@ -1,6 +1,6 @@
 # courtesy of https://github.com/timo/
 # see https://github.com/timo/talon_scripts
-from talon import Module, Context, app, canvas, screen, settings, ui, ctrl, cron
+from talon import Module, Context, actions, app, canvas, screen, settings, ui, ctrl, cron
 from talon.skia import Shader, Color, Paint, Rect
 from talon.types.point import Point2d
 from talon_plugins import eye_mouse, eye_zoom_mouse
@@ -279,7 +279,7 @@
     def grid_narrow_list(digit_list: typing.List[str]):
         """Choose fields multiple times in a row"""
         for d in digit_list:
-            GridActions.grid_narrow(int(d))
+            actions.self.grid_narrow(int(d))
 
     def grid_narrow(digit: Union[int, str]):
         """Choose a field of the grid and narrow the selection down"""

{"golden_diff": "diff --git a/mouse_grid/mouse_grid.py b/mouse_grid/mouse_grid.py\n--- a/mouse_grid/mouse_grid.py\n+++ b/mouse_grid/mouse_grid.py\n@@ -1,6 +1,6 @@\n # courtesy of https://github.com/timo/\n # see https://github.com/timo/talon_scripts\n-from talon import Module, Context, app, canvas, screen, settings, ui, ctrl, cron\n+from talon import Module, Context, actions, app, canvas, screen, settings, ui, ctrl, cron\n from talon.skia import Shader, Color, Paint, Rect\n from talon.types.point import Point2d\n from talon_plugins import eye_mouse, eye_zoom_mouse\n@@ -279,7 +279,7 @@\n     def grid_narrow_list(digit_list: typing.List[str]):\n         \"\"\"Choose fields multiple times in a row\"\"\"\n         for d in digit_list:\n-            GridActions.grid_narrow(int(d))\n+            actions.self.grid_narrow(int(d))\n \n     def grid_narrow(digit: Union[int, str]):\n         \"\"\"Choose a field of the grid and narrow the selection down\"\"\"\n", "issue": "Shouldn't call methods directly on an @action_class\nYou shouldn't call methods directly on an @action_class, for example:\r\n\r\nhttps://github.com/knausj85/knausj_talon/blob/65ccd94b8fe97056a34b858b2f1f261cc5269c3f/mouse_grid/mouse_grid.py#L282\r\n\r\nThis will continue to work, but generate a warning in the next beta.\r\n\r\nYou should probably use `actions.self.grid_narrow(...)` instead.\n", "before_files": [{"content": "# courtesy of https://github.com/timo/\n# see https://github.com/timo/talon_scripts\nfrom talon import Module, Context, app, canvas, screen, settings, ui, ctrl, cron\nfrom talon.skia import Shader, Color, Paint, Rect\nfrom talon.types.point import Point2d\nfrom talon_plugins import eye_mouse, eye_zoom_mouse\nfrom typing import Union\n\nimport math, time\n\nimport typing\n\nmod = Module()\nnarrow_expansion = mod.setting(\n    \"grid_narrow_expansion\",\n    type=int,\n    default=0,\n    desc=\"\"\"After narrowing, grow the new region by this many pixels in every direction, to make things immediately on edges easier to hit, and when the grid is at its smallest, it allows you to still nudge it around\"\"\",\n)\n\nmod.tag(\"mouse_grid_showing\", desc=\"Tag indicates whether the mouse grid is showing\")\nmod.tag(\"mouse_grid_enabled\", desc=\"Deprecated: do not use.  Activates legacy m grid command\")\nctx = Context()\n\n\nclass MouseSnapNine:\n    def __init__(self):\n        self.screen = None\n        self.rect = None\n        self.history = []\n        self.img = None\n        self.mcanvas = None\n        self.active = False\n        self.count = 0\n        self.was_control_mouse_active = False\n        self.was_zoom_mouse_active = False\n\n    def setup(self, *, rect: Rect = None, screen_num: int = None):\n        screens = ui.screens()\n        # each if block here might set the rect to None to indicate failure\n        if rect is not None:\n            try:\n                screen = ui.screen_containing(*rect.center)\n            except Exception:\n                rect = None\n        if rect is None and screen_num is not None:\n            screen = screens[screen_num % len(screens)]\n            rect = screen.rect\n        if rect is None:\n            screen = screens[0]\n            rect = screen.rect\n        self.rect = rect.copy()\n        self.screen = screen\n        self.count = 0\n        self.img = None\n        if self.mcanvas is not None:\n            self.mcanvas.close()\n        self.mcanvas = canvas.Canvas.from_screen(screen)\n        if self.active:\n            self.mcanvas.register(\"draw\", self.draw)\n            self.mcanvas.freeze()\n\n    def show(self):\n        if self.active:\n            return\n        # noinspection PyUnresolvedReferences\n        if eye_zoom_mouse.zoom_mouse.enabled:\n            self.was_zoom_mouse_active = True\n            eye_zoom_mouse.toggle_zoom_mouse(False)\n        if eye_mouse.control_mouse.enabled:\n            self.was_control_mouse_active = True\n            eye_mouse.control_mouse.toggle()\n        self.mcanvas.register(\"draw\", self.draw)\n        self.mcanvas.freeze()\n        self.active = True\n        return\n\n    def close(self):\n        if not self.active:\n            return\n        self.mcanvas.unregister(\"draw\", self.draw)\n        self.mcanvas.close()\n        self.mcanvas = None\n        self.img = None\n\n        self.active = False\n        if self.was_control_mouse_active and not eye_mouse.control_mouse.enabled:\n            eye_mouse.control_mouse.toggle()\n        if self.was_zoom_mouse_active and not eye_zoom_mouse.zoom_mouse.enabled:\n            eye_zoom_mouse.toggle_zoom_mouse(True)\n\n        self.was_zoom_mouse_active = False\n        self.was_control_mouse_active = False\n\n    def draw(self, canvas):\n        paint = canvas.paint\n\n        def draw_grid(offset_x, offset_y, width, height):\n            canvas.draw_line(\n                offset_x + width // 3,\n                offset_y,\n                offset_x + width // 3,\n                offset_y + height,\n            )\n            canvas.draw_line(\n                offset_x + 2 * width // 3,\n                offset_y,\n                offset_x + 2 * width // 3,\n                offset_y + height,\n            )\n\n            canvas.draw_line(\n                offset_x,\n                offset_y + height // 3,\n                offset_x + width,\n                offset_y + height // 3,\n            )\n            canvas.draw_line(\n                offset_x,\n                offset_y + 2 * height // 3,\n                offset_x + width,\n                offset_y + 2 * height // 3,\n            )\n\n        def draw_crosses(offset_x, offset_y, width, height):\n            for row in range(0, 2):\n                for col in range(0, 2):\n                    cx = offset_x + width / 6 + (col + 0.5) * width / 3\n                    cy = offset_y + height / 6 + (row + 0.5) * height / 3\n\n                    canvas.draw_line(cx - 10, cy, cx + 10, cy)\n                    canvas.draw_line(cx, cy - 10, cx, cy + 10)\n\n        grid_stroke = 1\n\n        def draw_text(offset_x, offset_y, width, height):\n            canvas.paint.text_align = canvas.paint.TextAlign.CENTER\n            for row in range(3):\n                for col in range(3):\n                    text_string = \"\"\n                    if settings[\"user.grids_put_one_bottom_left\"]:\n                        text_string = f\"{(2 - row)*3+col+1}\"\n                    else:\n                        text_string = f\"{row*3+col+1}\"\n                    text_rect = canvas.paint.measure_text(text_string)[1]\n                    background_rect = text_rect.copy()\n                    background_rect.center = Point2d(\n                        offset_x + width / 6 + col * width / 3,\n                        offset_y + height / 6 + row * height / 3,\n                    )\n                    background_rect = background_rect.inset(-4)\n                    paint.color = \"9999995f\"\n                    paint.style = Paint.Style.FILL\n                    canvas.draw_rect(background_rect)\n                    paint.color = \"00ff00ff\"\n                    canvas.draw_text(\n                        text_string,\n                        offset_x + width / 6 + col * width / 3,\n                        offset_y + height / 6 + row * height / 3 + text_rect.height / 2,\n                    )\n\n        if self.count < 2:\n            paint.color = \"00ff007f\"\n            for which in range(1, 10):\n                gap = 35 - self.count * 10\n                if not self.active:\n                    gap = 45\n                draw_crosses(*self.calc_narrow(which, self.rect))\n\n        paint.stroke_width = grid_stroke\n        if self.active:\n            paint.color = \"ff0000ff\"\n        else:\n            paint.color = \"000000ff\"\n        if self.count >= 2:\n            aspect = self.rect.width / self.rect.height\n            if aspect >= 1:\n                w = self.screen.width / 3\n                h = w / aspect\n            else:\n                h = self.screen.height / 3\n                w = h * aspect\n            x = self.screen.x + (self.screen.width - w) / 2\n            y = self.screen.y + (self.screen.height - h) / 2\n            self.draw_zoom(canvas, x, y, w, h)\n            draw_grid(x, y, w, h)\n            draw_text(x, y, w, h)\n        else:\n            draw_grid(self.rect.x, self.rect.y, self.rect.width, self.rect.height)\n\n            paint.textsize += 12 - self.count * 3\n            draw_text(self.rect.x, self.rect.y, self.rect.width, self.rect.height)\n\n    def calc_narrow(self, which, rect):\n        rect = rect.copy()\n        bdr = narrow_expansion.get()\n        row = int(which - 1) // 3\n        col = int(which - 1) % 3\n        if settings[\"user.grids_put_one_bottom_left\"]:\n            row = 2 - row\n        rect.x += int(col * rect.width // 3) - bdr\n        rect.y += int(row * rect.height // 3) - bdr\n        rect.width = (rect.width // 3) + bdr * 2\n        rect.height = (rect.height // 3) + bdr * 2\n        return rect\n\n    def narrow(self, which, move=True):\n        if which < 1 or which > 9:\n            return\n        self.save_state()\n        rect = self.calc_narrow(which, self.rect)\n        # check count so we don't bother zooming in _too_ far\n        if self.count < 5:\n            self.rect = rect.copy()\n            self.count += 1\n        if move:\n            ctrl.mouse_move(*rect.center)\n        if self.count >= 2:\n            self.update_screenshot()\n        else:\n            self.mcanvas.freeze()\n\n    def update_screenshot(self):\n        def finish_capture():\n            self.img = screen.capture_rect(self.rect)\n            self.mcanvas.freeze()\n\n        self.mcanvas.hide()\n        cron.after(\"16ms\", finish_capture)\n\n    def draw_zoom(self, canvas, x, y, w, h):\n        if self.img:\n            src = Rect(0, 0, self.img.width, self.img.height)\n            dst = Rect(x, y, w, h)\n            canvas.draw_image_rect(self.img, src, dst)\n\n    def narrow_to_pos(self, x, y):\n        col_size = int(self.width // 3)\n        row_size = int(self.height // 3)\n        col = math.floor((x - self.rect.x) / col_size)\n        row = math.floor((y - self.rect.x) / row_size)\n        self.narrow(1 + col + 3 * row, move=False)\n\n    def save_state(self):\n        self.history.append((self.count, self.rect.copy()))\n\n    def go_back(self):\n        # FIXME: need window and screen tracking\n        self.count, self.rect = self.history.pop()\n        self.mcanvas.freeze()\n\n\nmg = MouseSnapNine()\n\n\n@mod.action_class\nclass GridActions:\n    def grid_activate():\n        \"\"\"Show mouse grid\"\"\"\n        if not mg.mcanvas:\n            mg.setup()\n        mg.show()\n        ctx.tags = [\"user.mouse_grid_showing\"]\n\n    def grid_place_window():\n        \"\"\"Places the grid on the currently active window\"\"\"\n        mg.setup(rect=ui.active_window().rect)\n\n    def grid_reset():\n        \"\"\"Resets the grid to fill the whole screen again\"\"\"\n        if mg.active:\n            mg.setup()\n\n    def grid_select_screen(screen: int):\n        \"\"\"Brings up mouse grid\"\"\"\n        mg.setup(screen_num=screen - 1)\n        mg.show()\n\n    def grid_narrow_list(digit_list: typing.List[str]):\n        \"\"\"Choose fields multiple times in a row\"\"\"\n        for d in digit_list:\n            GridActions.grid_narrow(int(d))\n\n    def grid_narrow(digit: Union[int, str]):\n        \"\"\"Choose a field of the grid and narrow the selection down\"\"\"\n        mg.narrow(int(digit))\n\n    def grid_go_back():\n        \"\"\"Sets the grid state back to what it was before the last command\"\"\"\n        mg.go_back()\n\n    def grid_close():\n        \"\"\"Close the active grid\"\"\"\n        ctx.tags = []\n        mg.close()\n", "path": "mouse_grid/mouse_grid.py"}], "after_files": [{"content": "# courtesy of https://github.com/timo/\n# see https://github.com/timo/talon_scripts\nfrom talon import Module, Context, actions, app, canvas, screen, settings, ui, ctrl, cron\nfrom talon.skia import Shader, Color, Paint, Rect\nfrom talon.types.point import Point2d\nfrom talon_plugins import eye_mouse, eye_zoom_mouse\nfrom typing import Union\n\nimport math, time\n\nimport typing\n\nmod = Module()\nnarrow_expansion = mod.setting(\n    \"grid_narrow_expansion\",\n    type=int,\n    default=0,\n    desc=\"\"\"After narrowing, grow the new region by this many pixels in every direction, to make things immediately on edges easier to hit, and when the grid is at its smallest, it allows you to still nudge it around\"\"\",\n)\n\nmod.tag(\"mouse_grid_showing\", desc=\"Tag indicates whether the mouse grid is showing\")\nmod.tag(\"mouse_grid_enabled\", desc=\"Deprecated: do not use.  Activates legacy m grid command\")\nctx = Context()\n\n\nclass MouseSnapNine:\n    def __init__(self):\n        self.screen = None\n        self.rect = None\n        self.history = []\n        self.img = None\n        self.mcanvas = None\n        self.active = False\n        self.count = 0\n        self.was_control_mouse_active = False\n        self.was_zoom_mouse_active = False\n\n    def setup(self, *, rect: Rect = None, screen_num: int = None):\n        screens = ui.screens()\n        # each if block here might set the rect to None to indicate failure\n        if rect is not None:\n            try:\n                screen = ui.screen_containing(*rect.center)\n            except Exception:\n                rect = None\n        if rect is None and screen_num is not None:\n            screen = screens[screen_num % len(screens)]\n            rect = screen.rect\n        if rect is None:\n            screen = screens[0]\n            rect = screen.rect\n        self.rect = rect.copy()\n        self.screen = screen\n        self.count = 0\n        self.img = None\n        if self.mcanvas is not None:\n            self.mcanvas.close()\n        self.mcanvas = canvas.Canvas.from_screen(screen)\n        if self.active:\n            self.mcanvas.register(\"draw\", self.draw)\n            self.mcanvas.freeze()\n\n    def show(self):\n        if self.active:\n            return\n        # noinspection PyUnresolvedReferences\n        if eye_zoom_mouse.zoom_mouse.enabled:\n            self.was_zoom_mouse_active = True\n            eye_zoom_mouse.toggle_zoom_mouse(False)\n        if eye_mouse.control_mouse.enabled:\n            self.was_control_mouse_active = True\n            eye_mouse.control_mouse.toggle()\n        self.mcanvas.register(\"draw\", self.draw)\n        self.mcanvas.freeze()\n        self.active = True\n        return\n\n    def close(self):\n        if not self.active:\n            return\n        self.mcanvas.unregister(\"draw\", self.draw)\n        self.mcanvas.close()\n        self.mcanvas = None\n        self.img = None\n\n        self.active = False\n        if self.was_control_mouse_active and not eye_mouse.control_mouse.enabled:\n            eye_mouse.control_mouse.toggle()\n        if self.was_zoom_mouse_active and not eye_zoom_mouse.zoom_mouse.enabled:\n            eye_zoom_mouse.toggle_zoom_mouse(True)\n\n        self.was_zoom_mouse_active = False\n        self.was_control_mouse_active = False\n\n    def draw(self, canvas):\n        paint = canvas.paint\n\n        def draw_grid(offset_x, offset_y, width, height):\n            canvas.draw_line(\n                offset_x + width // 3,\n                offset_y,\n                offset_x + width // 3,\n                offset_y + height,\n            )\n            canvas.draw_line(\n                offset_x + 2 * width // 3,\n                offset_y,\n                offset_x + 2 * width // 3,\n                offset_y + height,\n            )\n\n            canvas.draw_line(\n                offset_x,\n                offset_y + height // 3,\n                offset_x + width,\n                offset_y + height // 3,\n            )\n            canvas.draw_line(\n                offset_x,\n                offset_y + 2 * height // 3,\n                offset_x + width,\n                offset_y + 2 * height // 3,\n            )\n\n        def draw_crosses(offset_x, offset_y, width, height):\n            for row in range(0, 2):\n                for col in range(0, 2):\n                    cx = offset_x + width / 6 + (col + 0.5) * width / 3\n                    cy = offset_y + height / 6 + (row + 0.5) * height / 3\n\n                    canvas.draw_line(cx - 10, cy, cx + 10, cy)\n                    canvas.draw_line(cx, cy - 10, cx, cy + 10)\n\n        grid_stroke = 1\n\n        def draw_text(offset_x, offset_y, width, height):\n            canvas.paint.text_align = canvas.paint.TextAlign.CENTER\n            for row in range(3):\n                for col in range(3):\n                    text_string = \"\"\n                    if settings[\"user.grids_put_one_bottom_left\"]:\n                        text_string = f\"{(2 - row)*3+col+1}\"\n                    else:\n                        text_string = f\"{row*3+col+1}\"\n                    text_rect = canvas.paint.measure_text(text_string)[1]\n                    background_rect = text_rect.copy()\n                    background_rect.center = Point2d(\n                        offset_x + width / 6 + col * width / 3,\n                        offset_y + height / 6 + row * height / 3,\n                    )\n                    background_rect = background_rect.inset(-4)\n                    paint.color = \"9999995f\"\n                    paint.style = Paint.Style.FILL\n                    canvas.draw_rect(background_rect)\n                    paint.color = \"00ff00ff\"\n                    canvas.draw_text(\n                        text_string,\n                        offset_x + width / 6 + col * width / 3,\n                        offset_y + height / 6 + row * height / 3 + text_rect.height / 2,\n                    )\n\n        if self.count < 2:\n            paint.color = \"00ff007f\"\n            for which in range(1, 10):\n                gap = 35 - self.count * 10\n                if not self.active:\n                    gap = 45\n                draw_crosses(*self.calc_narrow(which, self.rect))\n\n        paint.stroke_width = grid_stroke\n        if self.active:\n            paint.color = \"ff0000ff\"\n        else:\n            paint.color = \"000000ff\"\n        if self.count >= 2:\n            aspect = self.rect.width / self.rect.height\n            if aspect >= 1:\n                w = self.screen.width / 3\n                h = w / aspect\n            else:\n                h = self.screen.height / 3\n                w = h * aspect\n            x = self.screen.x + (self.screen.width - w) / 2\n            y = self.screen.y + (self.screen.height - h) / 2\n            self.draw_zoom(canvas, x, y, w, h)\n            draw_grid(x, y, w, h)\n            draw_text(x, y, w, h)\n        else:\n            draw_grid(self.rect.x, self.rect.y, self.rect.width, self.rect.height)\n\n            paint.textsize += 12 - self.count * 3\n            draw_text(self.rect.x, self.rect.y, self.rect.width, self.rect.height)\n\n    def calc_narrow(self, which, rect):\n        rect = rect.copy()\n        bdr = narrow_expansion.get()\n        row = int(which - 1) // 3\n        col = int(which - 1) % 3\n        if settings[\"user.grids_put_one_bottom_left\"]:\n            row = 2 - row\n        rect.x += int(col * rect.width // 3) - bdr\n        rect.y += int(row * rect.height // 3) - bdr\n        rect.width = (rect.width // 3) + bdr * 2\n        rect.height = (rect.height // 3) + bdr * 2\n        return rect\n\n    def narrow(self, which, move=True):\n        if which < 1 or which > 9:\n            return\n        self.save_state()\n        rect = self.calc_narrow(which, self.rect)\n        # check count so we don't bother zooming in _too_ far\n        if self.count < 5:\n            self.rect = rect.copy()\n            self.count += 1\n        if move:\n            ctrl.mouse_move(*rect.center)\n        if self.count >= 2:\n            self.update_screenshot()\n        else:\n            self.mcanvas.freeze()\n\n    def update_screenshot(self):\n        def finish_capture():\n            self.img = screen.capture_rect(self.rect)\n            self.mcanvas.freeze()\n\n        self.mcanvas.hide()\n        cron.after(\"16ms\", finish_capture)\n\n    def draw_zoom(self, canvas, x, y, w, h):\n        if self.img:\n            src = Rect(0, 0, self.img.width, self.img.height)\n            dst = Rect(x, y, w, h)\n            canvas.draw_image_rect(self.img, src, dst)\n\n    def narrow_to_pos(self, x, y):\n        col_size = int(self.width // 3)\n        row_size = int(self.height // 3)\n        col = math.floor((x - self.rect.x) / col_size)\n        row = math.floor((y - self.rect.x) / row_size)\n        self.narrow(1 + col + 3 * row, move=False)\n\n    def save_state(self):\n        self.history.append((self.count, self.rect.copy()))\n\n    def go_back(self):\n        # FIXME: need window and screen tracking\n        self.count, self.rect = self.history.pop()\n        self.mcanvas.freeze()\n\n\nmg = MouseSnapNine()\n\n\n@mod.action_class\nclass GridActions:\n    def grid_activate():\n        \"\"\"Show mouse grid\"\"\"\n        if not mg.mcanvas:\n            mg.setup()\n        mg.show()\n        ctx.tags = [\"user.mouse_grid_showing\"]\n\n    def grid_place_window():\n        \"\"\"Places the grid on the currently active window\"\"\"\n        mg.setup(rect=ui.active_window().rect)\n\n    def grid_reset():\n        \"\"\"Resets the grid to fill the whole screen again\"\"\"\n        if mg.active:\n            mg.setup()\n\n    def grid_select_screen(screen: int):\n        \"\"\"Brings up mouse grid\"\"\"\n        mg.setup(screen_num=screen - 1)\n        mg.show()\n\n    def grid_narrow_list(digit_list: typing.List[str]):\n        \"\"\"Choose fields multiple times in a row\"\"\"\n        for d in digit_list:\n            actions.self.grid_narrow(int(d))\n\n    def grid_narrow(digit: Union[int, str]):\n        \"\"\"Choose a field of the grid and narrow the selection down\"\"\"\n        mg.narrow(int(digit))\n\n    def grid_go_back():\n        \"\"\"Sets the grid state back to what it was before the last command\"\"\"\n        mg.go_back()\n\n    def grid_close():\n        \"\"\"Close the active grid\"\"\"\n        ctx.tags = []\n        mg.close()\n", "path": "mouse_grid/mouse_grid.py"}]}