这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "windows-monitor-functions"
version = "0.1.1"
version = "0.2.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,23 @@ it is the primary monitor
Untitled - Notepad is using monitor \\.\DISPLAY2
```

**Changing the primary monitor:**

You can use the `set_primary_monitor` function, which accepts a display name or you can use the `set_primary` method of a `Monitor` object to change the
primary monitor. If the monitor is already the primary monitor, no change will be made and the operation is considered successful. Returns `True` when successful and
`False` when not successful. If an invalid monitor name is given, an exception is raised.

```python
import wmutil
monitor: wmutil.Monitor # assume this is already defined

wmutil.set_primary_monitor(monitor.name)
# or
monitor.set_primary()
```



Notes:

- `monitor.size` may not necessarily reflect the monitor's resolution, but rather is the geometry used for drawing or moving windows
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = 'maturin'
[project]
name = "wmutil"
license = { file = "LICENSE" }
version = "0.1.1"
version = "0.2.0"
authors = [
{name = "Spencer Phillip Young", email="spencer.young@spyoung.com"}
]
Expand Down
101 changes: 98 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@ use std::{io, mem, ptr};
use std::collections::VecDeque;
use std::ffi::{c_void, OsString};
use std::hash::Hash;
use std::ops::BitAnd;
use std::ops::{BitAnd, Neg};
use std::ops::Deref;
use std::os::windows::prelude::OsStringExt;
use std::sync::OnceLock;

use std::mem::size_of;
use std::ptr::{null, null_mut};
use dpi::{PhysicalPosition, PhysicalSize};
use pyo3::prelude::*;
use pyo3::pymodule;
use windows_sys::core::HRESULT;
use windows_sys::Win32::Foundation::{BOOL, HWND, LPARAM, POINT, RECT, S_OK};
use windows_sys::Win32::Foundation::{BOOL, HWND, WPARAM, LPARAM, POINT, RECT, POINTL, S_OK};
use windows_sys::Win32::Graphics::Gdi::{
DEVMODEW, ENUM_CURRENT_SETTINGS, EnumDisplayMonitors, EnumDisplaySettingsExW,
GetMonitorInfoW, HDC,
HMONITOR, MONITOR_DEFAULTTONEAREST, MONITOR_DEFAULTTOPRIMARY, MonitorFromPoint, MonitorFromWindow, MONITORINFO,
MONITORINFOEXW,
};
use windows_sys::Win32::Graphics::Gdi::*;

use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA};
use windows_sys::Win32::UI::HiDpi::{
MDT_EFFECTIVE_DPI, MONITOR_DPI_TYPE,
Expand Down Expand Up @@ -242,6 +245,7 @@ unsafe extern "system" fn monitor_enum_proc(
// Python bindings

#[pyclass(module = "wmutil")]
#[derive(Clone)]
struct Monitor {
monitor_handle: MonitorHandle,
}
Expand Down Expand Up @@ -284,6 +288,11 @@ impl Monitor {
self.monitor_handle.0 as isize
}

pub fn set_primary(&self) -> PyResult<()> {
set_primary_monitor(self.name());
Ok(())
}

pub fn __hash__(&self) -> isize {
self.handle()
}
Expand Down Expand Up @@ -336,6 +345,91 @@ fn get_monitor_from_point(x: i32, y: i32) -> Monitor {
}
}

fn wide_string(s: &str) -> Vec<u16> {
let mut vec: Vec<u16> = s.encode_utf16().collect();
vec.push(0);
vec
}


fn get_dev_mode(display_name: &str) -> Result<DEVMODEW, String> {
let mut devmode: DEVMODEW = unsafe { std::mem::zeroed() };
devmode.dmSize = size_of::<DEVMODEW>() as u16;

let wide_name = wide_string(display_name);

let success = unsafe {
EnumDisplaySettingsW(wide_name.as_ptr(), ENUM_CURRENT_SETTINGS, &mut devmode)
};

if success == 0 {
return Err(format!("Failed to retrieve settings for display: {}", display_name));
}

Ok(devmode)
}


#[pyfunction]
fn set_primary_monitor(display_name: String) -> PyResult<bool> {
let all_monitors = enumerate_monitors();
let mut maybe_this_monitor: Option<Monitor> = None;
for monitor in all_monitors.clone() {
if monitor.name() == display_name {
maybe_this_monitor = Some(monitor);
break
}
}

// todo: raise a proper exception instead of a panic exception
assert!(maybe_this_monitor.is_some(), "Monitor with name {:?} not found", display_name);

let this_monitor = maybe_this_monitor.unwrap();

let (this_x, this_y) = this_monitor.position();

if (this_x == 0 && this_y == 0) {
// the requested monitor is already the primary monitor
return Ok(true)
}

let x_offset = this_x.neg();
let y_offset = this_y.neg();

let display_name_string = display_name.as_str();
let wide_name = wide_string(display_name_string);

for monitor in all_monitors.clone() {
if monitor.name() != display_name {
let mut devmode: DEVMODEW = get_dev_mode(monitor.name().as_str()).unwrap();
unsafe {
let (monitor_x, monitor_y) = monitor.position();
let new_x = monitor_x + x_offset;
let new_y = monitor_y + y_offset;
devmode.Anonymous1.Anonymous2.dmPosition = POINTL { x: new_x, y: new_y };
// println!("display: {} old: {} {} new: {} {}", monitor.name(), monitor_x, monitor_y, new_x, new_y);
ChangeDisplaySettingsExW(wide_string(monitor.name().as_str()).as_ptr(), &mut devmode, 0, CDS_UPDATEREGISTRY | CDS_NORESET, null_mut());
}
}
}
let mut devmode: DEVMODEW = get_dev_mode(display_name_string).unwrap();
unsafe {
// println!("{} being set as primary to 0 0", display_name);
devmode.Anonymous1.Anonymous2.dmPosition = POINTL { x: 0, y: 0 };
ChangeDisplaySettingsExW(wide_name.as_ptr(), &mut devmode, 0, CDS_SET_PRIMARY | CDS_UPDATEREGISTRY | CDS_NORESET, null_mut());
}

let result = unsafe {
ChangeDisplaySettingsExW(null_mut(), null_mut(), 0, 0, null_mut())
};
if result == DISP_CHANGE_SUCCESSFUL {
Ok(true)
} else {
Ok(false)
}
}



#[pymodule]
fn wmutil(_py: Python, m: &PyModule) -> PyResult<()> {
Expand All @@ -344,6 +438,7 @@ fn wmutil(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(get_window_monitor, m)?);
m.add_function(wrap_pyfunction!(get_primary_monitor, m)?);
m.add_function(wrap_pyfunction!(get_monitor_from_point, m)?);
m.add_function(wrap_pyfunction!(set_primary_monitor, m)?);

Ok(())
}
Expand Down
4 changes: 4 additions & 0 deletions wmutil.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ class Monitor:
@property
def handle(self) -> int: ...

def set_primary(self) -> None: ...


def get_primary_monitor() -> Monitor: ...
def get_window_monitor(hwnd: int) -> Monitor: ...
def enumerate_monitors() -> list[Monitor]: ...
def get_monitor_from_point(x: int, y: int) -> Monitor: ...

def set_primary_monitor(display_name: str) -> None: ...