Skip to content

Commit

Permalink
Make it public
Browse files Browse the repository at this point in the history
  • Loading branch information
ricardotejo committed Nov 6, 2019
1 parent f64a2a3 commit bf5606b
Show file tree
Hide file tree
Showing 16 changed files with 645 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

/.vs
/obj
/bin
9 changes: 9 additions & 0 deletions App.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Application x:Class="MonitorControl.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MonitorControl"
StartupUri="MainWindow.xaml">
<Application.Resources>

</Application.Resources>
</Application>
20 changes: 20 additions & 0 deletions App.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Runtime.InteropServices;
using System.Windows;

namespace MonitorControl
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public App()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
MessageBox.Show("This application runs only on Windows", "Monitor Control", MessageBoxButton.OK, MessageBoxImage.Exclamation);
Application.Current.Shutdown();
}
}
}
}
64 changes: 64 additions & 0 deletions MainWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<Window x:Class="MonitorControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MonitorControl"
mc:Ignorable="d"
Title="Monitor Control"
MinHeight="200" MinWidth="200" SizeToContent="WidthAndHeight" ResizeMode="NoResize"
MaxWidth="650"
Loaded="Window_Loaded"
>


<ListBox x:Name="MonitorList" ScrollViewer.HorizontalScrollBarVisibility="Disabled" SelectionMode="Single">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>

<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="160" Width="180" Margin="5" >
<Grid.RowDefinitions>
<RowDefinition Height="100"/>
<RowDefinition />
</Grid.RowDefinitions>
<Border BorderThickness="1" CornerRadius="3" Grid.RowSpan="2" BorderBrush="{DynamicResource {x:Static SystemColors.ScrollBarBrushKey}}" />
<Grid Grid.Row="0">
<TextBlock Text="{Binding DeviceName}" TextWrapping="Wrap" Grid.RowSpan="2" HorizontalAlignment="Center" Margin="0,10,0,0" >
</TextBlock>
<Rectangle HorizontalAlignment="Center" Margin="0,35,0,0" VerticalAlignment="Top"
Width="{Binding DisplayWidth}" Height="53">
<Rectangle.Stroke>
<SolidColorBrush Color="{DynamicResource {x:Static SystemColors.HighlightColorKey}}"/>
</Rectangle.Stroke>
</Rectangle>
</Grid>
<Grid Grid.Row="1" IsEnabled="{Binding IsEnabled}">
<Slider HorizontalAlignment="Left" Margin="10,0,0,0" VerticalAlignment="Center" Width="116" Height="24"
Maximum="100" SmallChange="1" TickPlacement="TopLeft" TickFrequency="25" IsMoveToPointEnabled="True"
Value="{Binding BrightnessLevel, Mode=OneWay}" ValueChanged="Slider_ValueChanged" />
<Button HorizontalAlignment="Right" VerticalAlignment="Center"
Width="32" Height="32" Margin="0,0,10,0"
Content="{Binding PowerText, Mode=OneWay}" Click="Button_Click" />
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

</Window>
88 changes: 88 additions & 0 deletions MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System.Collections.Generic;
using System.Windows;
using System.Linq;
using System.ComponentModel;
using System;

namespace MonitorControl
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

MonitorManager manager;

public List<MonitorData> Monitors = null;

private void Window_Loaded(object sender, RoutedEventArgs e)
{
manager = new MonitorManager();
manager.Initialize();

Monitors = manager.Monitors
.SelectMany(a => a.physicalMonitors.Select(b => new MonitorData(b, a.rect)))
.OrderBy(r => r.PositionX)
.ToList();

MonitorList.ItemsSource = Monitors;

}

private void Button_Click(object sender, RoutedEventArgs e)
{
FrameworkElement fe = sender as FrameworkElement;
MonitorData monitor = ((MonitorData)fe.DataContext);

monitor.SwitchPower();
manager.ChangePower(monitor.Ref.hPhysicalMonitor, monitor.IsPoweredOn);


}

private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
FrameworkElement fe = sender as FrameworkElement;
MonitorData monitor = ((MonitorData)fe.DataContext);

monitor.Ref.BrightnessLevel = (uint)e.NewValue;

manager.ChangeBrightness(monitor.Ref.hPhysicalMonitor, monitor.BrightnessLevel);
}
}

public class MonitorData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

public MonitorData(PhysicalMonitor physicalMonitor, Rect rect)
{
this.Ref = physicalMonitor;
IsLandspace = (rect.Right - rect.Left) / (float)(rect.Bottom - rect.Top) > 1.0f;
PositionX = rect.Left;
}

public PhysicalMonitor Ref { get; private set; }
public string DeviceName { get { return Ref.IsEnabled ? Ref.DeviceName : "Generic Monitor"; } }
public bool IsEnabled { get { return Ref.IsEnabled; } }
public bool IsPoweredOn { get { return Ref.IsEnabled ? Ref.IsPoweredOn : false; } }
public uint BrightnessLevel { get { return Ref.IsEnabled ? Ref.BrightnessLevel : 0; } }

public bool IsLandspace { get; private set; }
public int PositionX { get; private set; }
public int DisplayWidth { get { return IsLandspace ? 96 : 30; } }
public string PowerText { get { return IsPoweredOn ? "OFF" : "ON"; } }

internal void SwitchPower()
{
this.Ref.IsPoweredOn = !this.Ref.IsPoweredOn;
PropertyChanged(this, new PropertyChangedEventArgs("PowerText"));
}
}

}
113 changes: 113 additions & 0 deletions MonitorApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace MonitorControl
{




public class MonitorApi : IDisposable
{

public void TEST()
{
List<(IntPtr ptr, Rect rect, int d)> hMonitors = new List<(IntPtr, Rect, int)>();

MonitorEnumProc callback = (IntPtr hDesktop, IntPtr hdc, ref Rect prect, int d) =>
{
hMonitors.Add((hDesktop, prect, d));
return true;
};

List<PHYSICAL_MONITOR> monitors = new List<PHYSICAL_MONITOR>();
if (EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, callback, 0))
{
foreach (var m in hMonitors)
{
uint mcount = 0;
if (!GetNumberOfPhysicalMonitorsFromHMONITOR(m.ptr, ref mcount))
{
throw new Exception("Cannot get monitor count!");
}
PHYSICAL_MONITOR[] physicalMonitors = new PHYSICAL_MONITOR[mcount];

if (!GetPhysicalMonitorsFromHMONITOR(m.ptr, mcount, physicalMonitors))
{
throw new Exception("Cannot get phisical monitor handle!");
}

Debug.WriteLine($"PM:{physicalMonitors.Length}) RECT: T:{m.rect.top}/L:{m.rect.left}/R:{m.rect.right}/B:{m.rect.bottom} SCALE:{m.d}");
monitors.AddRange(physicalMonitors);
}

for (int i = 0; i < monitors.Count; i++)
{

uint cv = 0, mv = 0;
if (GetVCPFeatureAndVCPFeatureReply(monitors[i].hPhysicalMonitor, SVC_FEATURE__POWER_MODE, IntPtr.Zero, ref cv, ref mv))
{
Debug.WriteLine($"{i}) {monitors[i].hPhysicalMonitor} + {monitors[i].szPhysicalMonitorDescription} + `{cv}/{mv}`");
}
else
{
string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
Debug.WriteLine($"{i}) ERROR: {errorMessage}");

// TODO: use HighLevel API to set brightness on these monitors
// https://docs.microsoft.com/en-us/windows/win32/api/highlevelmonitorconfigurationapi/nf-highlevelmonitorconfigurationapi-setmonitorbrightness
}
}

// call power

//:: Current value> 1=On / 2=Standby / 3=Suspended / 4=Off / 5=OffByButton
MonitorSwitch(monitors[2], PowerModeEnum.PowerOn); // <<<< OK but doesn't work on laptop screen
//MonitorBrightness(monitors[0], 60); // <<<< OK but doesn't work on laptop screen

PHYSICAL_MONITOR[] toDestroy = monitors.ToArray();
DestroyPhysicalMonitors((uint)toDestroy.Length, ref toDestroy);
}
else
{
//error
}

}



//Switch monitor power
void MonitorSwitch(PHYSICAL_MONITOR monitor, PowerModeEnum mode)
{
if (SetVCPFeature(monitor.hPhysicalMonitor, SVC_FEATURE__POWER_MODE, (uint)mode))
{
Debug.WriteLine("works!");
}
else
{
string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
Debug.WriteLine(errorMessage);
}
}


//Switch monitor power
void MonitorBrightness(PHYSICAL_MONITOR monitor, uint pctg)
{
if (SetVCPFeature(monitor.hPhysicalMonitor, SVC_FEATURE__Brightness, (uint)pctg))
{
Debug.WriteLine("works!");
}
else
{
string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
Debug.WriteLine(errorMessage);
}
}

}
}
25 changes: 25 additions & 0 deletions MonitorControl.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29424.173
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonitorControlNetCore", "MonitorControlNetCore.csproj", "{656F438D-C75C-4FA4-A24A-04A7B611C1A1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{656F438D-C75C-4FA4-A24A-04A7B611C1A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{656F438D-C75C-4FA4-A24A-04A7B611C1A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{656F438D-C75C-4FA4-A24A-04A7B611C1A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{656F438D-C75C-4FA4-A24A-04A7B611C1A1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5862A0AD-D119-48E7-9671-25DBDA502B9B}
EndGlobalSection
EndGlobal
25 changes: 25 additions & 0 deletions MonitorControlNetCore.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<UseWPF>true</UseWPF>
<RootNamespace>MonitorControl</RootNamespace>
<AssemblyName>MonitorControl</AssemblyName>
<StartupObject>MonitorControl.App</StartupObject>
<PackageId>com.ricardotejo.monitor-control</PackageId>
<Authors>Ricardo Tejo</Authors>
<Company>Ricardo Tejo</Company>
<Product>Monitor Control</Product>
<Description>Brightness and power control for multiple physical monitors</Description>
<Copyright>©2019 Ricardo Tejo</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<SignAssembly>false</SignAssembly>
<ApplicationIcon>icon.ico</ApplicationIcon>
</PropertyGroup>

<ItemGroup>
<Compile Remove="MonitorApi.cs" />
</ItemGroup>

</Project>
16 changes: 16 additions & 0 deletions MonitorControlNetCore.csproj.user
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<_LastSelectedProfileId>D:\personal\MonitorControl\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
</PropertyGroup>
<ItemGroup>
<ApplicationDefinition Update="App.xaml">
<SubType>Designer</SubType>
</ApplicationDefinition>
</ItemGroup>
<ItemGroup>
<Page Update="MainWindow.xaml">
<SubType>Designer</SubType>
</Page>
</ItemGroup>
</Project>
Loading

0 comments on commit bf5606b

Please sign in to comment.