# Screen Utilities
> **Relevant source files**
> * [src/io/github/samera2022/mouse_macros/Localizer.java](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/Localizer.java)
> * [src/io/github/samera2022/mouse_macros/MouseMacro.java](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/MouseMacro.java)
> * [src/io/github/samera2022/mouse_macros/constant/OtherConsts.java](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/constant/OtherConsts.java)
> * [src/io/github/samera2022/mouse_macros/listener/GlobalMouseListener.java](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/listener/GlobalMouseListener.java)
> * [src/io/github/samera2022/mouse_macros/util/FileUtil.java](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/util/FileUtil.java)
## Purpose and Scope
The `ScreenUtil` class provides coordinate transformation utilities for multi-monitor setups and DPI scaling handling. This utility enables MouseMacros to record and play back mouse actions consistently across different monitor configurations by normalizing coordinates to a virtual origin that spans all connected displays.
For information about other utility classes, see [Utility Classes](System-Integration-Utilities). For details on how these utilities integrate with macro playback, see [MouseAction](MouseAction-Data-Structure).
**Sources:** [src/io/github/samera2022/mouse_macros/util/ScreenUtil.java L1-L42](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/util/ScreenUtil.java#L1-L42)
## Multi-Monitor Coordinate Systems
In Java AWT, each monitor has its own coordinate space. The primary monitor typically has its origin at `(0, 0)`, while secondary monitors may have negative or positive offsets depending on their physical arrangement. The virtual screen space encompasses all monitors as a single continuous coordinate system.
### Virtual Origin Concept
The virtual origin is the top-left corner of the bounding rectangle that contains all connected monitors. This point has the minimum X and Y coordinates across all screens and serves as the reference point for coordinate normalization.
```mermaid
flowchart TD
M1Origin["Origin: (-1920, 0)
Size: 1920x1080"]
M2Origin["Origin: (0, 0)
Size: 2560x1440"]
M3Origin["Origin: (2560, -200)
Size: 1920x1080"]
VirtualOrigin["Virtual Origin
Point(-1920, -200)
minX across all screens
minY across all screens"]
Note1["Normalized coordinates
use virtual origin as (0,0)"]
Note2["Global coordinates
use primary screen as (0,0)"]
VirtualOrigin --> M1Origin
VirtualOrigin --> M2Origin
VirtualOrigin --> M3Origin
VirtualOrigin --> Note1
M2Origin --> Note2
subgraph subGraph3 ["Multi-Monitor Layout Example"]
subgraph subGraph2 ["Monitor3[Monitor 3 - Secondary]"]
M3Origin
end
subgraph subGraph1 ["Monitor2[Monitor 2 - Primary]"]
M2Origin
end
subgraph subGraph0 ["Monitor1[Monitor 1 - Secondary]"]
M1Origin
end
end
```
**Sources:** [src/io/github/samera2022/mouse_macros/util/ScreenUtil.java L24-L35](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/util/ScreenUtil.java#L24-L35)
## Coordinate Transformation Methods
The `ScreenUtil` class provides three coordinate transformation methods to enable consistent macro recording and playback across different monitor configurations.
### Method Overview
| Method | Input | Output | Purpose |
| --- | --- | --- | --- |
| `normalizeToVirtualOrigin` | Global coordinates | Normalized coordinates | Record phase: Store coordinates relative to virtual origin |
| `denormalizeFromVirtualOrigin` | Normalized coordinates | Robot coordinates | Playback phase: Convert to primary screen coordinates for Robot |
| `getVirtualOrigin` | None | Virtual origin point | Calculate the minimum X/Y across all screens |
### Coordinate Transformation Flow
```mermaid
sequenceDiagram
participant User
participant Operating System
participant JNativeHook
participant GlobalMouseListener
participant ScreenUtil
participant MouseAction
participant java.awt.Robot
note over User,java.awt.Robot: Recording Phase
User->>Operating System: Mouse click at screen position
Operating System->>JNativeHook: Global coordinates (x, y)
JNativeHook->>GlobalMouseListener: nativeMousePressed(x, y)
GlobalMouseListener->>ScreenUtil: normalizeToVirtualOrigin(x, y)
ScreenUtil->>ScreenUtil: getVirtualOrigin()
ScreenUtil->>ScreenUtil: normalized = (x - minX, y - minY)
ScreenUtil-->>GlobalMouseListener: Normalized Point
GlobalMouseListener->>MouseAction: Create MouseAction with normalized coords
MouseAction->>MouseAction: Store normalized coordinates
note over User,java.awt.Robot: Playback Phase
User->>GlobalMouseListener: Trigger playback hotkey
GlobalMouseListener->>MouseAction: action.perform()
MouseAction->>ScreenUtil: denormalizeFromVirtualOrigin(x, y)
ScreenUtil->>ScreenUtil: getVirtualOrigin()
ScreenUtil->>ScreenUtil: globalX = x + virtualOrigin.x
ScreenUtil->>ScreenUtil: globalY = y + virtualOrigin.y
ScreenUtil->>ScreenUtil: Get primary screen bounds
ScreenUtil->>ScreenUtil: Get DPI scale factors
ScreenUtil->>ScreenUtil: robotX = (globalX - primaryX) / scaleX
ScreenUtil->>ScreenUtil: robotY = (globalY - primaryY) / scaleY
ScreenUtil-->>MouseAction: Robot-compatible Point
MouseAction->>java.awt.Robot: mouseMove(robotX, robotY)
java.awt.Robot->>Operating System: Inject mouse event
```
**Sources:** [src/io/github/samera2022/mouse_macros/util/ScreenUtil.java L7-L41](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/util/ScreenUtil.java#L7-L41)
### Normalization: Global to Virtual Coordinates
The `normalizeToVirtualOrigin` method converts global screen coordinates to coordinates relative to the virtual origin. This is used during macro recording to store coordinates in a monitor-configuration-independent format.
```mermaid
flowchart TD
Input["Input
Global Coordinates
(x, y)"]
GetOrigin["getVirtualOrigin()
Returns (minX, minY)"]
Calculate["Calculate
normalizedX = x - minX
normalizedY = y - minY"]
Output["Output
Normalized Point
(x - minX, y - minY)"]
Input --> GetOrigin
GetOrigin --> Calculate
Calculate --> Output
```
**Implementation:** [src/io/github/samera2022/mouse_macros/util/ScreenUtil.java L38-L41](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/util/ScreenUtil.java#L38-L41)
**Sources:** [src/io/github/samera2022/mouse_macros/util/ScreenUtil.java L37-L41](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/util/ScreenUtil.java#L37-L41)
### Denormalization: Virtual to Robot Coordinates
The `denormalizeFromVirtualOrigin` method converts normalized coordinates back to the coordinate system expected by `java.awt.Robot`. This is more complex than normalization because it must:
1. Convert from virtual origin coordinates to global coordinates
2. Convert from global coordinates to primary screen coordinates
3. Apply DPI scaling corrections
```mermaid
flowchart TD
Input["Input: Normalized Coordinates
(x, y) relative to virtual origin"]
Step1["Step 1: Get Virtual Origin
virtualOrigin = getVirtualOrigin()"]
Step2["Step 2: Convert to Global Coordinates
globalX = x + virtualOrigin.x
globalY = y + virtualOrigin.y"]
Step3["Step 3: Get Primary Screen Info
gc = defaultScreenDevice.defaultConfiguration
primaryBounds = gc.getBounds()
scaleX = gc.getDefaultTransform().getScaleX()
scaleY = gc.getDefaultTransform().getScaleY()"]
Step4["Step 4: Convert to Primary Screen Coords
relativeX = globalX - primaryBounds.x
relativeY = globalY - primaryBounds.y"]
Step5["Step 5: Apply DPI Scaling
robotX = round(relativeX / scaleX)
robotY = round(relativeY / scaleY)"]
Output["Output: Robot Coordinates
(robotX, robotY)
Ready for java.awt.Robot"]
Input --> Step1
Step1 --> Step2
Step2 --> Step3
Step3 --> Step4
Step4 --> Step5
Step5 --> Output
```
**Implementation:** [src/io/github/samera2022/mouse_macros/util/ScreenUtil.java L7-L22](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/util/ScreenUtil.java#L7-L22)
**Sources:** [src/io/github/samera2022/mouse_macros/util/ScreenUtil.java L6-L22](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/util/ScreenUtil.java#L6-L22)
## Virtual Origin Calculation
The `getVirtualOrigin` method calculates the top-left corner of the virtual screen space by finding the minimum X and Y coordinates across all connected displays.
### Algorithm
```mermaid
flowchart TD
Start["Start: getVirtualOrigin()"]
Init["Initialize
minX = Integer.MAX_VALUE
minY = Integer.MAX_VALUE"]
GetEnv["Get GraphicsEnvironment
ge = getLocalGraphicsEnvironment()"]
GetDevices["Get all GraphicsDevice[]
devices = ge.getScreenDevices()"]
LoopStart["For each
device"]
GetBounds["Get device bounds
bounds = device.getDefaultConfiguration().getBounds()"]
UpdateMinX["bounds.x < minX?"]
SetMinX["minX = bounds.x"]
UpdateMinY["bounds.y < minY?"]
SetMinY["minY = bounds.y"]
LoopEnd["Next device"]
Return["Return Point(minX, minY)"]
Start --> Init
Init --> GetEnv
GetEnv --> GetDevices
GetDevices --> LoopStart
LoopStart --> GetBounds
GetBounds --> UpdateMinX
UpdateMinX --> SetMinX
UpdateMinX --> UpdateMinY
SetMinX --> UpdateMinY
UpdateMinY --> SetMinY
UpdateMinY --> LoopEnd
SetMinY --> LoopEnd
LoopEnd --> LoopStart
LoopStart --> Return
```
**Implementation:** [src/io/github/samera2022/mouse_macros/util/ScreenUtil.java L25-L35](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/util/ScreenUtil.java#L25-L35)
The method iterates through all connected displays and tracks the minimum X and Y values. This ensures that all screen coordinates can be represented as positive offsets from the virtual origin.
**Sources:** [src/io/github/samera2022/mouse_macros/util/ScreenUtil.java L24-L35](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/util/ScreenUtil.java#L24-L35)
## DPI Scaling Handling
Modern operating systems support high-DPI displays with scaling factors (e.g., 150%, 200%). Java's `java.awt.Robot` class expects coordinates in the physical pixel space, not the logical coordinate space used by most applications.
### DPI Scaling Transformation
The `denormalizeFromVirtualOrigin` method handles DPI scaling by:
1. Obtaining the default transform from the primary screen's `GraphicsConfiguration`
2. Extracting X and Y scale factors using `getScaleX()` and `getScaleY()`
3. Dividing the relative coordinates by the scale factors
4. Rounding to the nearest integer pixel
```mermaid
flowchart TD
LogicalCoords["Logical Coordinates
(e.g., 1000, 500)
at 200% scaling"]
ScaleFactor["Scale Factors
scaleX = 2.0
scaleY = 2.0"]
Division["Division
physicalX = 1000 / 2.0 = 500
physicalY = 500 / 2.0 = 250"]
Rounding["Rounding
robotX = round(500) = 500
robotY = round(250) = 250"]
PhysicalCoords["Physical Coordinates
(500, 250)
for Robot API"]
LogicalCoords --> ScaleFactor
ScaleFactor --> Division
Division --> Rounding
Rounding --> PhysicalCoords
```
**Implementation:** [src/io/github/samera2022/mouse_macros/util/ScreenUtil.java L16-L20](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/util/ScreenUtil.java#L16-L20)
### Why DPI Scaling Matters
| Without DPI Handling | With DPI Handling |
| --- | --- |
| Robot clicks at wrong location | Robot clicks at correct location |
| Coordinates off by scale factor | Coordinates match recorded position |
| Macros fail on high-DPI displays | Macros work consistently |
| User must manually adjust | Automatic correction applied |
**Sources:** [src/io/github/samera2022/mouse_macros/util/ScreenUtil.java L13-L21](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/util/ScreenUtil.java#L13-L21)
## Integration with Macro System
The `ScreenUtil` class integrates with the macro recording and playback pipeline through the `MouseAction` class.
### Recording Integration
During recording, `GlobalMouseListener` receives mouse events with global screen coordinates from `JNativeHook`. Before creating a `MouseAction` object, the coordinates are normalized using `normalizeToVirtualOrigin`. This ensures that recorded coordinates are independent of the user's monitor configuration at recording time.
### Playback Integration
During playback, the `MouseAction.perform()` method calls `denormalizeFromVirtualOrigin` to convert the stored normalized coordinates back to coordinates that `java.awt.Robot` can use. This transformation accounts for:
* Changes in monitor configuration since recording
* DPI scaling differences between recording and playback systems
* Primary screen position differences
```mermaid
flowchart TD
RecEvent["Mouse Event
Global Coords"]
Normalize["ScreenUtil.normalizeToVirtualOrigin"]
StoreAction["Store MouseAction
with normalized coords"]
MMCFile[".mmc File
CSV format
x,y,type,..."]
LoadAction["Load MouseAction
from file"]
Denormalize["ScreenUtil.denormalizeFromVirtualOrigin"]
RobotExec["Robot.mouseMove
Robot.mousePress"]
StoreAction --> MMCFile
MMCFile --> LoadAction
subgraph Playback ["Playback"]
LoadAction
Denormalize
RobotExec
LoadAction --> Denormalize
Denormalize --> RobotExec
end
subgraph Persistence ["Persistence"]
MMCFile
end
subgraph Recording ["Recording"]
RecEvent
Normalize
StoreAction
RecEvent --> Normalize
Normalize --> StoreAction
end
```
**Sources:** [src/io/github/samera2022/mouse_macros/util/ScreenUtil.java L1-L42](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/util/ScreenUtil.java#L1-L42)
## Class Structure
The `ScreenUtil` class is a utility class with static methods only. It has no instance variables or constructors.
### Public Methods
| Method Signature | Return Type | Description |
| --- | --- | --- |
| `denormalizeFromVirtualOrigin(int x, int y)` | `Point` | Converts normalized coordinates to Robot-compatible coordinates with DPI scaling |
| `normalizeToVirtualOrigin(int x, int y)` | `Point` | Converts global screen coordinates to normalized coordinates |
### Private Methods
| Method Signature | Return Type | Description |
| --- | --- | --- |
| `getVirtualOrigin()` | `Point` | Calculates the minimum X and Y coordinates across all screens |
**Sources:** [src/io/github/samera2022/mouse_macros/util/ScreenUtil.java L5-L42](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/util/ScreenUtil.java#L5-L42)
## Usage Examples
### Recording Phase Usage
When `GlobalMouseListener` receives a mouse event during recording:
```yaml
Input: nativeMousePressed event at global coordinates (2500, 800)
Call: ScreenUtil.normalizeToVirtualOrigin(2500, 800)
Virtual Origin: (-1920, -200)
Output: Point(4420, 1000) stored in MouseAction
```
### Playback Phase Usage
When `MouseAction.perform()` is called during playback:
```python
Input: Normalized coordinates (4420, 1000) from stored action
Call: ScreenUtil.denormalizeFromVirtualOrigin(4420, 1000)
Virtual Origin: (-1920, -200)
Global Coords: (2500, 800)
Primary Screen: origin (0, 0), scale (2.0, 2.0)
Robot Coords: (1250, 400)
Output: Robot.mouseMove(1250, 400)
```
**Sources:** [src/io/github/samera2022/mouse_macros/util/ScreenUtil.java L7-L41](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/util/ScreenUtil.java#L7-L41)
## Technical Considerations
### Thread Safety
The `ScreenUtil` class methods are stateless and thread-safe. Each method invocation queries the current graphics environment state and performs calculations without modifying shared state.
### Performance
The `getVirtualOrigin` method is called during each coordinate transformation. This involves querying all connected displays through the Java AWT API. For typical macro playback with delays between actions, this overhead is negligible.
### Coordinate Precision
Coordinate calculations use integer arithmetic for X and Y values. The DPI scaling division uses floating-point arithmetic with `Math.round()` to ensure accurate conversion to physical pixel coordinates.
### Monitor Configuration Changes
If the user changes their monitor configuration (adds/removes displays, changes arrangement) between recording and playback, the normalized coordinates ensure macros still execute at the correct relative positions. However, if the recording was made on a screen that no longer exists, the action may execute outside the visible screen area.
**Sources:** [src/io/github/samera2022/mouse_macros/util/ScreenUtil.java L1-L42](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/util/ScreenUtil.java#L1-L42)