├── Client ├── src │ ├── shims-vue.d.ts │ ├── dtos │ │ ├── parameters.ts │ │ ├── keyBindingDto.ts │ │ ├── configurations │ │ │ ├── colorConfigurationDto.ts │ │ │ ├── temperatureConfigurationDto.ts │ │ │ └── configurationDto.ts │ │ └── screenDto.ts │ ├── assets │ │ ├── font-files │ │ │ └── InterVariable.woff2 │ │ └── vue.svg │ ├── components │ │ ├── SquareButton.vue │ │ ├── DeletePopover.vue │ │ ├── Pages │ │ │ ├── CategorySelectionPage.vue │ │ │ ├── KeyBindingsPage.vue │ │ │ ├── ConfigurationsPage.vue │ │ │ ├── ParametersPage.vue │ │ │ └── CreateOrUpdateKeyBindingPage.vue │ │ └── ScreensViewer.vue │ ├── main.ts │ ├── style.css │ ├── composables │ │ ├── useSignalR.ts │ │ └── useScreens.ts │ ├── router.ts │ ├── App.vue │ └── global.ts ├── .env ├── postcss.config.cjs ├── .vscode │ ├── extensions.json │ └── settings.json ├── tailwind.config.cjs ├── tsconfig.node.json ├── vite.config.ts ├── .gitignore ├── index.html ├── tsconfig.json ├── .eslintrc.json ├── package.json └── README.md ├── Screenshots ├── hotkey.jpg ├── landing_page.jpg └── configuration.jpg ├── Server ├── Resources │ └── icon.ico ├── appsettings.Development.json ├── appsettings.json ├── App.xaml ├── DTOs │ ├── Configurations │ │ ├── ColorConfigurationDto.cs │ │ ├── TemperatureConfigurationDto.cs │ │ └── ConfigurationDto.cs │ ├── KeyBindingWithHotKeyRegistrationResultDto.cs │ ├── APIErrorResponseDto.cs │ ├── KeyBindingDto.cs │ ├── ParametersDto.cs │ └── ScreenDto.cs ├── Entities │ ├── Configurations │ │ ├── ColorConfiguration.cs │ │ ├── TemperatureConfiguration.cs │ │ └── Configuration.cs │ ├── Parameter.cs │ ├── KeyBinding.cs │ └── Screen.cs ├── Properties │ └── launchSettings.json ├── MainWindow.xaml ├── .editorconfig ├── Controllers │ ├── ScreenController.cs │ ├── ParametersController.cs │ ├── KeyBindingController.cs │ └── ConfigurationController.cs ├── Mappers │ ├── KeyBindingMapper.cs │ ├── ScreenMapper.cs │ └── ConfigurationMapper.cs ├── Middlewares │ └── ExceptionMiddleware.cs ├── Migrations │ ├── 20250126194909_add-name-on-key-binding.cs │ ├── 20250215165711_apply-configuration-at-startup.cs │ ├── 20250215192516_minimize-on-startup-parameter.cs │ ├── 20250209165047_multiple-configurations-per-binding.cs │ ├── 20250209150504_remove-commands.cs │ ├── 20250209150504_remove-commands.Designer.cs │ ├── 20250209165047_multiple-configurations-per-binding.Designer.cs │ ├── 20250215165711_apply-configuration-at-startup.Designer.cs │ ├── DatabaseContextModelSnapshot.cs │ ├── 20250215192516_minimize-on-startup-parameter.Designer.cs │ ├── 20250118151747_InitialCreate.cs │ ├── 20250118151747_InitialCreate.Designer.cs │ └── 20250126194909_add-name-on-key-binding.Designer.cs ├── ScreenTemperature.sln ├── App.xaml.cs ├── Hubs │ └── Hub.cs ├── DatabaseContext.cs ├── ScreenTemperature.csproj ├── MainWindow.xaml.cs ├── Resources.Designer.cs ├── Services │ ├── ParametersService.cs │ └── ScreenService.cs ├── Resources.resx ├── HotKeyManager.cs ├── .gitignore ├── WebApp.cs └── Win32.cs ├── .gitignore ├── makefile ├── README.md └── innoSetup.iss /Client/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue'; -------------------------------------------------------------------------------- /Client/.env: -------------------------------------------------------------------------------- 1 | VITE_SERVER_BASE_URL=http://localhost:61983 2 | VITE_APP_VERSION=$npm_package_version -------------------------------------------------------------------------------- /Screenshots/hotkey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/massaiTHEdog1/ScreenTemperature/HEAD/Screenshots/hotkey.jpg -------------------------------------------------------------------------------- /Server/Resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/massaiTHEdog1/ScreenTemperature/HEAD/Server/Resources/icon.ico -------------------------------------------------------------------------------- /Screenshots/landing_page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/massaiTHEdog1/ScreenTemperature/HEAD/Screenshots/landing_page.jpg -------------------------------------------------------------------------------- /Screenshots/configuration.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/massaiTHEdog1/ScreenTemperature/HEAD/Screenshots/configuration.jpg -------------------------------------------------------------------------------- /Client/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /Client/src/dtos/parameters.ts: -------------------------------------------------------------------------------- 1 | export interface ParametersDto { 2 | startApplicationOnUserLogin: boolean; 3 | minimizeOnStartup: boolean; 4 | } -------------------------------------------------------------------------------- /Client/src/assets/font-files/InterVariable.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/massaiTHEdog1/ScreenTemperature/HEAD/Client/src/assets/font-files/InterVariable.woff2 -------------------------------------------------------------------------------- /Client/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "bradlc.vscode-tailwindcss", 5 | "vue.volar" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /Server/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Client/src/dtos/keyBindingDto.ts: -------------------------------------------------------------------------------- 1 | export interface KeyBindingDto { 2 | id: string; 3 | name: string; 4 | configurationIds: string[]; 5 | keyCode: number; 6 | alt: boolean; 7 | control: boolean; 8 | } -------------------------------------------------------------------------------- /Server/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /Client/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{vue,js,ts,jsx,tsx}", 6 | ], 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /Client/src/dtos/configurations/colorConfigurationDto.ts: -------------------------------------------------------------------------------- 1 | import { ConfigurationDto } from "./configurationDto"; 2 | 3 | export interface ColorConfigurationDto extends ConfigurationDto { 4 | applyColor: boolean; 5 | color: string; 6 | } 7 | -------------------------------------------------------------------------------- /Client/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /Client/src/dtos/configurations/temperatureConfigurationDto.ts: -------------------------------------------------------------------------------- 1 | import { ConfigurationDto } from "./configurationDto"; 2 | 3 | export interface TemperatureConfigurationDto extends ConfigurationDto { 4 | applyIntensity: boolean; 5 | intensity: number; 6 | } 7 | -------------------------------------------------------------------------------- /Server/App.xaml: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | /.vs 6 | Output 7 | -------------------------------------------------------------------------------- /Server/DTOs/Configurations/ColorConfigurationDto.cs: -------------------------------------------------------------------------------- 1 | namespace ScreenTemperature.DTOs.Configurations 2 | { 3 | public class ColorConfigurationDto : ConfigurationDto 4 | { 5 | public bool ApplyColor { get; set; } 6 | public string Color { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Server/Entities/Configurations/ColorConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace ScreenTemperature.Entities.Configurations 2 | { 3 | public class ColorConfiguration : Configuration 4 | { 5 | public bool ApplyColor { get; set; } 6 | public string Color { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Server/DTOs/KeyBindingWithHotKeyRegistrationResultDto.cs: -------------------------------------------------------------------------------- 1 | namespace ScreenTemperature.DTOs 2 | { 3 | public class KeyBindingWithHotKeyRegistrationResultDto 4 | { 5 | public KeyBindingDto KeyBinding { get; set; } 6 | public bool IsHotKeyRegistered { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Server/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "ScreenTemperature": { 4 | "commandName": "Project", 5 | "dotnetRunMessages": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Server/Entities/Configurations/TemperatureConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace ScreenTemperature.Entities.Configurations 2 | { 3 | public class TemperatureConfiguration : Configuration 4 | { 5 | public bool ApplyIntensity { get; set; } 6 | public int Intensity { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Server/DTOs/Configurations/TemperatureConfigurationDto.cs: -------------------------------------------------------------------------------- 1 | namespace ScreenTemperature.DTOs.Configurations 2 | { 3 | public class TemperatureConfigurationDto : ConfigurationDto 4 | { 5 | public bool ApplyIntensity { get; set; } 6 | public int Intensity { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Server/DTOs/APIErrorResponseDto.cs: -------------------------------------------------------------------------------- 1 | namespace ScreenTemperature.DTOs 2 | { 3 | public class APIErrorResponseDto 4 | { 5 | public string[] Errors { get; set; } 6 | 7 | public APIErrorResponseDto(string[] errors) 8 | { 9 | Errors = errors; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Client/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import vue from '@vitejs/plugin-vue'; 3 | import * as path from 'path'; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [vue()], 8 | resolve: { 9 | alias: [ 10 | { find: '@', replacement: path.resolve(__dirname, 'src') }, 11 | ], 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /Server/DTOs/KeyBindingDto.cs: -------------------------------------------------------------------------------- 1 | namespace ScreenTemperature.DTOs; 2 | 3 | public class KeyBindingDto 4 | { 5 | public Guid Id { get; set; } 6 | public string Name { get; set; } 7 | public List ConfigurationIds { get; set; } = []; 8 | public int KeyCode { get; set; } 9 | public bool Alt { get; set; } 10 | public bool Control { get; set; } 11 | } -------------------------------------------------------------------------------- /Client/src/dtos/screenDto.ts: -------------------------------------------------------------------------------- 1 | export interface ScreenDto { 2 | devicePath: string; 3 | label: string; 4 | isPrimary: boolean; 5 | x: number; 6 | y: number; 7 | width: number; 8 | height: number; 9 | isDDCSupported: boolean; 10 | isBrightnessSupported: boolean; 11 | minBrightness: number; 12 | maxBrightness: number; 13 | currentBrightness: number; 14 | } 15 | -------------------------------------------------------------------------------- /Client/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | !.vscode/settings.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /Client/src/dtos/configurations/configurationDto.ts: -------------------------------------------------------------------------------- 1 | export enum ConfigurationDiscriminator { 2 | TemperatureConfiguration = "temperature", 3 | ColorConfiguration = "color", 4 | } 5 | 6 | export interface ConfigurationDto { 7 | $type: ConfigurationDiscriminator; 8 | id: string; 9 | name: string; 10 | devicePath: string; 11 | applyBrightness: boolean; 12 | brightness: number; 13 | applyAtStartup: boolean; 14 | } 15 | -------------------------------------------------------------------------------- /Server/Entities/Parameter.cs: -------------------------------------------------------------------------------- 1 | 2 | using ScreenTemperature.Entities.Configurations; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace ScreenTemperature.Entities; 7 | 8 | public class Parameter 9 | { 10 | [Key] 11 | [DatabaseGenerated(DatabaseGeneratedOption.None)] 12 | public string Key { get; set; } 13 | public string Value { get; set; } 14 | } -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | publish: publish_vue publish_dotnet move_vue_to_dotnet generate_installer 2 | 3 | publish_vue: 4 | cd .\Client && npm run build 5 | 6 | publish_dotnet: 7 | cd .\Server && dotnet publish 8 | 9 | move_vue_to_dotnet: 10 | if exist .\Server\bin\Release\net8.0-windows\wwwroot rd /s /q ".\Server\bin\Release\net8.0-windows\wwwroot" 11 | xcopy ".\Client\dist\" ".\Server\bin\Release\net8.0-windows\wwwroot\" /e /s /y 12 | 13 | generate_installer: 14 | iscc "innoSetup.iss" -------------------------------------------------------------------------------- /Client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ScreenTemperature 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /Client/src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Client/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.defaultFormatter": "dbaeumer.vscode-eslint", 4 | "eslint.format.enable": true, 5 | "[css]": { 6 | "editor.defaultFormatter": "vscode.css-language-features" 7 | }, 8 | "[typescript]": { 9 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 10 | }, 11 | "[javascript]": { 12 | "editor.defaultFormatter": "vscode.typescript-language-features" 13 | }, 14 | "[json]": { 15 | "editor.defaultFormatter": "vscode.json-language-features" 16 | } 17 | } -------------------------------------------------------------------------------- /Server/DTOs/ParametersDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ScreenTemperature.DTOs 8 | { 9 | public class ParametersDto 10 | { 11 | /// 12 | /// Returns whether the application should be started when the user logs in. 13 | /// 14 | public bool StartApplicationOnUserLogin { get; set; } 15 | 16 | public bool MinimizeOnStartup { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Server/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Server/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CS8618: Un champ non-nullable doit contenir une valeur non-null lors de la fermeture du constructeur. Envisagez de déclarer le champ comme nullable. 4 | dotnet_diagnostic.CS8618.severity = silent 5 | 6 | # CS8601: Existence possible d'une assignation de référence null. 7 | dotnet_diagnostic.CS8601.severity = silent 8 | 9 | # CA1416: Valider la compatibilité de la plateforme 10 | dotnet_diagnostic.CA1416.severity = silent 11 | 12 | # CS1591: Missing XML comment for publicly visible type or member 13 | dotnet_diagnostic.CS1591.severity = silent 14 | -------------------------------------------------------------------------------- /Server/Entities/Configurations/Configuration.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations.Schema; 2 | 3 | namespace ScreenTemperature.Entities.Configurations; 4 | 5 | public abstract class Configuration 6 | { 7 | [DatabaseGenerated(DatabaseGeneratedOption.None)] 8 | public Guid Id { get; set; } 9 | public string Name { get; set; } 10 | 11 | public string DevicePath { get; set; } 12 | 13 | public bool ApplyBrightness { get; set; } 14 | public byte Brightness { get; set; } 15 | 16 | public List KeyBindings { get; set; } = []; 17 | 18 | public bool ApplyAtStartup { get; set; } 19 | } -------------------------------------------------------------------------------- /Server/Controllers/ScreenController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Http.HttpResults; 4 | using Microsoft.AspNetCore.Mvc; 5 | using ScreenTemperature.DTOs; 6 | using ScreenTemperature.Mappers; 7 | using ScreenTemperature.Services; 8 | 9 | public class ScreenController(IScreenService screenService) 10 | { 11 | [HttpGet("/api/screens")] 12 | public async Task GetAllScreensAsync() 13 | { 14 | var screens = await screenService.GetScreensAsync(); 15 | 16 | return TypedResults.Ok(screens.Select(x => x.ToDto())); 17 | } 18 | } -------------------------------------------------------------------------------- /Server/Mappers/KeyBindingMapper.cs: -------------------------------------------------------------------------------- 1 | using ScreenTemperature.DTOs; 2 | using ScreenTemperature.Entities; 3 | 4 | namespace ScreenTemperature.Mappers 5 | { 6 | public static class KeyBindingMapper 7 | { 8 | public static KeyBindingDto ToDto(this KeyBinding entity) 9 | { 10 | return new KeyBindingDto() 11 | { 12 | Id = entity.Id, 13 | Name = entity.Name, 14 | ConfigurationIds = entity.Configurations.Select(x => x.Id).ToList(), 15 | Alt = entity.Alt, 16 | Control = entity.Control, 17 | KeyCode = entity.KeyCode, 18 | }; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Server/DTOs/ScreenDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ScreenTemperature.DTOs 4 | { 5 | public class ScreenDto 6 | { 7 | public string DevicePath { get; set; } 8 | public string Label { get; set; } 9 | public bool IsPrimary { get; set; } 10 | public int X { get; set; } 11 | public int Y { get; set; } 12 | public int Width { get; set; } 13 | public int Height { get; set; } 14 | public bool IsDDCSupported { get; set; } 15 | public bool IsBrightnessSupported { get; set; } 16 | public int MaxBrightness { get; set; } 17 | public int MinBrightness { get; set; } 18 | public int CurrentBrightness { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "resolveJsonModule": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "lib": ["ESNext", "DOM"], 13 | "skipLibCheck": true, 14 | "noEmit": true, 15 | "types": [ 16 | "vite/client", // if using vite 17 | // ... 18 | ], 19 | "paths": { 20 | "@/*": ["./src/*"], 21 | } 22 | }, 23 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /Client/src/components/SquareButton.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 32 | 33 | -------------------------------------------------------------------------------- /Client/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | import "./style.css"; 4 | import 'primeicons/primeicons.css'; 5 | import PrimeVue from 'primevue/config'; 6 | import { VueQueryPlugin } from '@tanstack/vue-query'; 7 | import router from "@/router"; 8 | import "@fortawesome/fontawesome-free/css/all.min.css"; 9 | import Tooltip from 'primevue/tooltip'; 10 | import Aura from '@primevue/themes/aura'; 11 | import ToastService from 'primevue/toastservice'; 12 | 13 | createApp(App) 14 | .directive('tooltip', Tooltip) 15 | .use(router) 16 | .use(PrimeVue, { 17 | theme: { 18 | preset: Aura, 19 | options: { 20 | darkModeSelector: '.my-app-dark' 21 | } 22 | } 23 | }) 24 | .use(VueQueryPlugin) 25 | .use(ToastService) 26 | .mount("#app"); 27 | -------------------------------------------------------------------------------- /Client/src/style.css: -------------------------------------------------------------------------------- 1 | /* @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; */ 4 | 5 | 6 | @layer tailwind-base, primevue, tailwind-utilities; 7 | 8 | @layer tailwind-base { 9 | @tailwind base; 10 | } 11 | 12 | @layer tailwind-utilities { 13 | @tailwind components; 14 | @tailwind utilities; 15 | } 16 | 17 | @font-face { 18 | font-family: Inter var; 19 | src: url('@/assets/font-files/InterVariable.woff2'); 20 | } 21 | 22 | html { 23 | font-feature-settings: "cv02", "cv03", "cv04", "cv11"; 24 | font-family: Inter var, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; 25 | font-size: 14px; 26 | line-height: normal; 27 | } 28 | 29 | .p-dialog { 30 | @apply !bg-slate-700 !border-0 !text-white; 31 | } -------------------------------------------------------------------------------- /Server/DTOs/Configurations/ConfigurationDto.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Annotations; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace ScreenTemperature.DTOs.Configurations 5 | { 6 | [SwaggerDiscriminator("$type")] 7 | [SwaggerSubType(typeof(TemperatureConfigurationDto), DiscriminatorValue = "temperature")] 8 | [JsonDerivedType(typeof(TemperatureConfigurationDto), "temperature")] 9 | [SwaggerSubType(typeof(ColorConfigurationDto), DiscriminatorValue = "color")] 10 | [JsonDerivedType(typeof(ColorConfigurationDto), "color")] 11 | public abstract class ConfigurationDto 12 | { 13 | public Guid Id { get; set; } 14 | public string Name { get; set; } 15 | public string DevicePath { get; set; } 16 | public bool ApplyBrightness { get; set; } 17 | public byte Brightness { get; set; } 18 | public bool ApplyAtStartup { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Server/Middlewares/ExceptionMiddleware.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.Hosting; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace ScreenTemperature.Middlewares; 9 | 10 | public class ExceptionMiddleware(RequestDelegate next, ILogger logger, IWebHostEnvironment webHostEnvironment) 11 | { 12 | 13 | public async Task Invoke(HttpContext context) 14 | { 15 | try 16 | { 17 | await next.Invoke(context); 18 | } 19 | catch (Exception ex) 20 | { 21 | logger.LogError(ex, ex.Message); 22 | 23 | context.Response.StatusCode = 500; 24 | 25 | if(webHostEnvironment.IsDevelopment()) 26 | { 27 | await context.Response.WriteAsJsonAsync(new { message = ex.Message }); 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Server/Migrations/20250126194909_add-name-on-key-binding.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace ScreenTemperature.Migrations 6 | { 7 | /// 8 | public partial class addnameonkeybinding : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "Name", 15 | table: "KeyBindings", 16 | type: "TEXT", 17 | nullable: false, 18 | defaultValue: ""); 19 | } 20 | 21 | /// 22 | protected override void Down(MigrationBuilder migrationBuilder) 23 | { 24 | migrationBuilder.DropColumn( 25 | name: "Name", 26 | table: "KeyBindings"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:@typescript-eslint/recommended", 5 | "plugin:vue/vue3-strongly-recommended" 6 | ], 7 | "parser": "vue-eslint-parser", 8 | "parserOptions": { 9 | "parser": "@typescript-eslint/parser", 10 | "ecmaVersion": 2020, 11 | "ecmaFeatures": { 12 | "jsx": true 13 | } 14 | }, 15 | "rules": { 16 | "vue/multi-word-component-names": "off", 17 | "semi": "error", // error when line not ending with ; 18 | "vue/attribute-hyphenation": ["error", "never"], 19 | "indent": "off", 20 | "@typescript-eslint/indent": ["error", 2], // set indent to 2, 21 | "@typescript-eslint/no-empty-interface": ["error", { 22 | "allowSingleExtends": true 23 | } 24 | ], 25 | "@typescript-eslint/no-non-null-assertion": ["off"], 26 | "vue/no-use-v-if-with-v-for": ["off"] 27 | }, 28 | "plugins": ["@typescript-eslint"] 29 | } -------------------------------------------------------------------------------- /Server/Controllers/ParametersController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Http.HttpResults; 4 | using Microsoft.AspNetCore.Mvc; 5 | using ScreenTemperature; 6 | using ScreenTemperature.DTOs; 7 | using ScreenTemperature.Entities; 8 | using ScreenTemperature.Mappers; 9 | using ScreenTemperature.Services; 10 | using System.ComponentModel.DataAnnotations; 11 | 12 | public class ParametersController(IParametersService parametersService) 13 | { 14 | [HttpGet("/api/parameters")] 15 | public async Task GetParametersAsync() 16 | { 17 | return TypedResults.Ok(await parametersService.GetParametersAsync()); 18 | } 19 | 20 | [HttpPut("/api/parameters")] 21 | public async Task UpdateParametersAsync([FromBody][Required] ParametersDto parameters) 22 | { 23 | return TypedResults.Ok(await parametersService.UpdateParametersAsync(parameters)); 24 | } 25 | } -------------------------------------------------------------------------------- /Server/Migrations/20250215165711_apply-configuration-at-startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace ScreenTemperature.Migrations 6 | { 7 | /// 8 | public partial class applyconfigurationatstartup : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "ApplyAtStartup", 15 | table: "Configurations", 16 | type: "INTEGER", 17 | nullable: false, 18 | defaultValue: false); 19 | } 20 | 21 | /// 22 | protected override void Down(MigrationBuilder migrationBuilder) 23 | { 24 | migrationBuilder.DropColumn( 25 | name: "ApplyAtStartup", 26 | table: "Configurations"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Client/src/composables/useSignalR.ts: -------------------------------------------------------------------------------- 1 | import { HubConnection, HubConnectionBuilder } from "@microsoft/signalr"; 2 | import { onUnmounted, ref } from "vue"; 3 | 4 | const connection = ref(new HubConnectionBuilder() 5 | .withUrl(`${import.meta.env.VITE_SERVER_BASE_URL}/hub`) 6 | .withAutomaticReconnect() 7 | //.configureLogging(LogLevel.Trace) 8 | .build()); 9 | 10 | connection.value.start(); 11 | 12 | export const useSignalR = () => { 13 | 14 | const registrations = ref<{ methodName: string, action: (...args: any) => unknown }[]>([]); 15 | 16 | const on = (methodName: string, action: (...args: any) => unknown) => { 17 | 18 | connection.value.on(methodName, action); 19 | 20 | registrations.value.push({ 21 | methodName, 22 | action 23 | }); 24 | }; 25 | 26 | onUnmounted(() => { 27 | for(const element of registrations.value) 28 | { 29 | connection.value.off(element.methodName); 30 | } 31 | }); 32 | 33 | return { connection, on }; 34 | }; -------------------------------------------------------------------------------- /Server/Mappers/ScreenMapper.cs: -------------------------------------------------------------------------------- 1 | using ScreenTemperature.DTOs; 2 | using ScreenTemperature.DTOs.Configurations; 3 | using ScreenTemperature.Entities; 4 | using ScreenTemperature.Entities.Configurations; 5 | 6 | namespace ScreenTemperature.Mappers 7 | { 8 | public static class ScreenMapperExtensions 9 | { 10 | public static ScreenDto ToDto(this Screen entity) 11 | { 12 | return new ScreenDto() 13 | { 14 | DevicePath = entity.DevicePath, 15 | Height = entity.Height, 16 | Width = entity.Width, 17 | IsPrimary = entity.IsPrimary, 18 | X = entity.X, 19 | Y = entity.Y, 20 | Label = entity.Label, 21 | IsDDCSupported = entity.IsDDCSupported, 22 | IsBrightnessSupported = entity.IsBrightnessSupported, 23 | MinBrightness = (int)entity.MinBrightness, 24 | MaxBrightness = (int)entity.MaxBrightness, 25 | CurrentBrightness = (int)entity.CurrentBrightness 26 | }; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Server/Entities/KeyBinding.cs: -------------------------------------------------------------------------------- 1 | 2 | using ScreenTemperature.Entities.Configurations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace ScreenTemperature.Entities; 6 | 7 | /// 8 | /// Key binding 9 | /// 10 | public class KeyBinding 11 | { 12 | /// 13 | /// Returns the identifier. 14 | /// 15 | [DatabaseGenerated(DatabaseGeneratedOption.None)] 16 | public Guid Id { get; set; } 17 | 18 | /// 19 | /// Returns the configurations associated to this binding. 20 | /// 21 | public List Configurations { get; set; } = []; 22 | 23 | public string Name { get; set; } 24 | 25 | /// 26 | /// Return the key that executes the . 27 | /// 28 | public int KeyCode { get; set; } 29 | 30 | /// 31 | /// Returns whether the Alt key should be pressed. 32 | /// 33 | public bool Alt { get; set; } 34 | 35 | /// 36 | /// Returns whether the Ctrl should be pressed. 37 | /// 38 | public bool Control { get; set; } 39 | } -------------------------------------------------------------------------------- /Client/src/components/DeletePopover.vue: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /Server/Migrations/20250215192516_minimize-on-startup-parameter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace ScreenTemperature.Migrations 6 | { 7 | /// 8 | public partial class minimizeonstartupparameter : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.CreateTable( 14 | name: "Parameters", 15 | columns: table => new 16 | { 17 | Key = table.Column(type: "TEXT", nullable: false), 18 | Value = table.Column(type: "TEXT", nullable: false) 19 | }, 20 | constraints: table => 21 | { 22 | table.PrimaryKey("PK_Parameters", x => x.Key); 23 | }); 24 | } 25 | 26 | /// 27 | protected override void Down(MigrationBuilder migrationBuilder) 28 | { 29 | migrationBuilder.DropTable( 30 | name: "Parameters"); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Server/ScreenTemperature.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32819.101 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenTemperature", "ScreenTemperature.csproj", "{9D0B4E17-1CD0-4880-948D-273DA09FC33C}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {9D0B4E17-1CD0-4880-948D-273DA09FC33C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {9D0B4E17-1CD0-4880-948D-273DA09FC33C}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {9D0B4E17-1CD0-4880-948D-273DA09FC33C}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {9D0B4E17-1CD0-4880-948D-273DA09FC33C}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {B6DFE9DF-5661-4738-A96A-BD391873C269} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "screen-temperature", 3 | "private": true, 4 | "version": "1.0.3", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vue-tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@fortawesome/fontawesome-free": "^6.5.2", 13 | "@fortawesome/fontawesome-svg-core": "^6.5.2", 14 | "@microsoft/signalr": "^8.0.7", 15 | "@primevue/themes": "^4.2.5", 16 | "@tanstack/vue-query": "^5.40.0", 17 | "primeicons": "^7.0.0", 18 | "primevue": "^4.2.5", 19 | "uuid": "^11.0.5", 20 | "vue": "^3.4.27", 21 | "vue-router": "^4.1.6" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^22.10.7", 25 | "@typescript-eslint/eslint-plugin": "^5.62.0", 26 | "@typescript-eslint/parser": "^5.62.0", 27 | "@vitejs/plugin-vue": "^5.2.1", 28 | "@vitejs/plugin-vue-jsx": "^4.1.1", 29 | "@vue/babel-plugin-jsx": "^1.2.5", 30 | "autoprefixer": "^10.4.20", 31 | "eslint": "^8.47.0", 32 | "eslint-plugin-vue": "^9.15.1", 33 | "globals": "^15.14.0", 34 | "postcss": "^8.4.21", 35 | "tailwindcss": "^3.4.17", 36 | "typescript": "^5.7.3", 37 | "vite": "^6.0.7", 38 | "vue-eslint-parser": "^9.4.3", 39 | "vue-tsc": "^2.2.0" 40 | } 41 | } -------------------------------------------------------------------------------- /Client/src/components/Pages/CategorySelectionPage.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 46 | 47 | -------------------------------------------------------------------------------- /Server/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Hosting; 5 | using Microsoft.Extensions.Logging; 6 | using ScreenTemperature.Hubs; 7 | using ScreenTemperature.Middlewares; 8 | using ScreenTemperature.Services; 9 | using System.Diagnostics; 10 | using System.IO; 11 | using System.Threading; 12 | using System.Windows; 13 | using Vernou.Swashbuckle.HttpResultsAdapter; 14 | 15 | namespace ScreenTemperature 16 | { 17 | public partial class App : Application 18 | { 19 | private CancellationTokenSource _cancellationTokenSource; 20 | public static string DataFolder = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "ScreenTemperature"); 21 | 22 | public App() 23 | { 24 | #if DEBUG 25 | Win32.AllocConsole();// add a console for server debug 26 | #endif 27 | 28 | _cancellationTokenSource = new CancellationTokenSource(); 29 | HotKeyManager.Instance.Start(_cancellationTokenSource.Token);// start hotkey manager 30 | WebApp.Instance.Start(_cancellationTokenSource.Token);// start server 31 | } 32 | 33 | protected async override void OnExit(ExitEventArgs e) 34 | { 35 | _cancellationTokenSource.Cancel();// close server 36 | await Task.WhenAll(HotKeyManager.Instance.Task, WebApp.Instance.Task); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Server/Hubs/Hub.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.SignalR; 2 | using Microsoft.Win32; 3 | using ScreenTemperature.Services; 4 | using System.Diagnostics; 5 | using System.Reflection; 6 | 7 | namespace ScreenTemperature.Hubs; 8 | 9 | public class Hub(IScreenService screenService, IParametersService optionsService) : Microsoft.AspNetCore.SignalR.Hub 10 | { 11 | public async Task ApplyBrightness(int brightness, string devicePath) 12 | { 13 | var result = false; 14 | 15 | try 16 | { 17 | result = await screenService.ApplyBrightnessToScreenAsync(brightness, devicePath); 18 | } 19 | catch (Exception ex) { } 20 | 21 | await Clients.All.SendAsync("ApplyBrightnessResult", result); 22 | } 23 | 24 | public async Task ApplyTemperature(int temperature, string devicePath) 25 | { 26 | var result = false; 27 | 28 | try 29 | { 30 | result = await screenService.ApplyKelvinToScreenAsync(temperature, devicePath); 31 | } 32 | catch (Exception ex) { } 33 | 34 | await Clients.All.SendAsync("ApplyTemperatureResult", result); 35 | } 36 | 37 | public async Task ApplyColor(string color, string devicePath) 38 | { 39 | var result = false; 40 | 41 | try 42 | { 43 | result = await screenService.ApplyColorToScreenAsync(color, devicePath); 44 | } 45 | catch (Exception ex) { } 46 | 47 | await Clients.All.SendAsync("ApplyColorResult", result); 48 | } 49 | } -------------------------------------------------------------------------------- /Client/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + TypeScript + Vite 2 | 3 | This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` 18 | 19 | 59 | 60 | -------------------------------------------------------------------------------- /Client/src/components/Pages/ConfigurationsPage.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 59 | 60 | -------------------------------------------------------------------------------- /Client/src/components/ScreensViewer.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 49 | 50 | 75 | -------------------------------------------------------------------------------- /Client/src/router.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from "@/global"; 2 | import { RouteRecordRaw, createRouter, createWebHistory } from "vue-router"; 3 | import CategorySelectionPage from "./components/Pages/CategorySelectionPage.vue"; 4 | import ConfigurationsPage from "./components/Pages/ConfigurationsPage.vue"; 5 | import CreateOrUpdateConfigurationPage from "./components/Pages/CreateOrUpdateConfigurationPage.vue"; 6 | import CreateOrUpdateKeyBindingPage from "./components/Pages/CreateOrUpdateKeyBindingPage.vue"; 7 | import KeyBindingsPage from "./components/Pages/KeyBindingsPage.vue"; 8 | import ParametersPage from "./components/Pages/ParametersPage.vue"; 9 | 10 | const routes: RouteRecordRaw[] = [ 11 | { 12 | name: Routes.CATEGORY_SELECTION, 13 | path: "/", 14 | component: CategorySelectionPage, 15 | children: [ 16 | { 17 | name: Routes.CONFIGURATIONS, 18 | path: "configurations", 19 | component: ConfigurationsPage, 20 | children: [ 21 | { 22 | name: Routes.CONFIGURATIONS_CREATE, 23 | path: "create", 24 | component: CreateOrUpdateConfigurationPage 25 | }, 26 | { 27 | name: Routes.CONFIGURATIONS_UPDATE, 28 | path: ":id", 29 | component: CreateOrUpdateConfigurationPage, 30 | props: route => ({ id: route.params.id }) 31 | } 32 | ] 33 | }, 34 | { 35 | name: Routes.KEY_BINDINGS, 36 | path: "bindings", 37 | component: KeyBindingsPage, 38 | children: [ 39 | { 40 | name: Routes.KEY_BINDING_CREATE, 41 | path: "create", 42 | component: CreateOrUpdateKeyBindingPage 43 | }, 44 | { 45 | name: Routes.KEY_BINDING_UPDATE, 46 | path: ":id", 47 | component: CreateOrUpdateKeyBindingPage, 48 | props: route => ({ id: route.params.id }) 49 | } 50 | ] 51 | }, 52 | { 53 | name: Routes.PARAMETERS, 54 | path: "parameters", 55 | component: ParametersPage, 56 | }, 57 | ] 58 | }, 59 | ]; 60 | 61 | const router = createRouter({ 62 | history: createWebHistory(), 63 | routes, // short for `routes: routes` 64 | }); 65 | 66 | export default router; 67 | -------------------------------------------------------------------------------- /Server/DatabaseContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using ScreenTemperature.Entities; 6 | using ScreenTemperature.Entities.Configurations; 7 | using System.IO; 8 | using Path = System.IO.Path; 9 | 10 | namespace ScreenTemperature; 11 | 12 | public class DatabaseContext : DbContext 13 | { 14 | private readonly IWebHostEnvironment _webHostEnvironment; 15 | 16 | public DbSet KeyBindings { get; set; } 17 | 18 | public DbSet Configurations { get; set; } 19 | public DbSet TemperatureConfigurations { get; set; } 20 | public DbSet ColorConfigurations { get; set; } 21 | public DbSet Parameters { get; set; } 22 | 23 | public string DbPath { get; } 24 | 25 | private string DatabasePath 26 | { 27 | get 28 | { 29 | if (_webHostEnvironment.IsDevelopment()) 30 | return "database.db"; 31 | else 32 | return Path.Join(App.DataFolder, "database.db"); 33 | } 34 | } 35 | 36 | public DatabaseContext(IWebHostEnvironment webHostEnvironment) 37 | { 38 | _webHostEnvironment = webHostEnvironment; 39 | 40 | var databaseDirectoryPath = Path.GetDirectoryName(DatabasePath); 41 | 42 | if (!string.IsNullOrWhiteSpace(databaseDirectoryPath) && !Directory.Exists(databaseDirectoryPath)) 43 | { 44 | Directory.CreateDirectory(databaseDirectoryPath); 45 | } 46 | 47 | DbPath = DatabasePath; 48 | } 49 | 50 | // The following configures EF to create a Sqlite database file in the 51 | // special "local" folder for your platform. 52 | protected override void OnConfiguring(DbContextOptionsBuilder options) 53 | { 54 | options.UseSqlite($"Data Source={DbPath}"); 55 | 56 | if (_webHostEnvironment.IsDevelopment()) 57 | { 58 | options.LogTo(Console.WriteLine, LogLevel.Information) 59 | .EnableDetailedErrors(true) 60 | .EnableSensitiveDataLogging(true); 61 | } 62 | } 63 | 64 | protected override void OnModelCreating(ModelBuilder modelBuilder) 65 | { 66 | modelBuilder.Entity().UseTptMappingStrategy(); // for polymorphism 67 | } 68 | } -------------------------------------------------------------------------------- /Server/ScreenTemperature.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0-windows 5 | 1.0.3 6 | WinExe 7 | true 8 | enable 9 | enable 10 | true 11 | ca417f13-91e3-4c65-8017-cdbc5e6f073e 12 | false 13 | ScreenTemperature.App 14 | Resources\icon.ico 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | all 29 | runtime; build; native; contentfiles; analyzers; buildtransitive 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | True 41 | True 42 | Resources.resx 43 | 44 | 45 | 46 | 47 | 48 | ResXFileCodeGenerator 49 | Resources.Designer.cs 50 | 51 | 52 | 53 | 54 | 55 | Always 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Client/src/App.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 68 | 69 | 73 | -------------------------------------------------------------------------------- /innoSetup.iss: -------------------------------------------------------------------------------- 1 | ; Script generated by the Inno Setup Script Wizard. 2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! 3 | 4 | #define MyAppName "ScreenTemperature" 5 | #define MyAppVersion "1.0.3" 6 | #define MyAppPublisher "massaiTHEdog" 7 | #define MyAppURL "https://github.com/massaiTHEdog1/ScreenTemperature" 8 | #define MyAppExeName "ScreenTemperature.exe" 9 | 10 | [Setup] 11 | ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. 12 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 13 | AppId={{7E09CD9B-CACA-4F76-AE2B-8A416E3C1FC1} 14 | AppName={#MyAppName} 15 | AppVersion={#MyAppVersion} 16 | ;AppVerName={#MyAppName} {#MyAppVersion} 17 | AppPublisher={#MyAppPublisher} 18 | AppPublisherURL={#MyAppURL} 19 | AppSupportURL={#MyAppURL} 20 | AppUpdatesURL={#MyAppURL} 21 | DefaultDirName={autopf}\{#MyAppName} 22 | UninstallDisplayIcon={app}\{#MyAppExeName} 23 | ; "ArchitecturesAllowed=x64compatible" specifies that Setup cannot run 24 | ; on anything but x64 and Windows 11 on Arm. 25 | ArchitecturesAllowed=x64compatible 26 | ; "ArchitecturesInstallIn64BitMode=x64compatible" requests that the 27 | ; install be done in "64-bit mode" on x64 or Windows 11 on Arm, 28 | ; meaning it should use the native 64-bit Program Files directory and 29 | ; the 64-bit view of the registry. 30 | ArchitecturesInstallIn64BitMode=x64compatible 31 | DisableProgramGroupPage=yes 32 | ; Remove the following line to run in administrative install mode (install for all users). 33 | PrivilegesRequired=lowest 34 | OutputBaseFilename=setup 35 | SolidCompression=yes 36 | WizardStyle=modern 37 | SignTool=MsSign $f 38 | CloseApplications=force 39 | 40 | [Languages] 41 | Name: "english"; MessagesFile: "compiler:Default.isl" 42 | 43 | [Tasks] 44 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked 45 | 46 | [Files] 47 | Source: ".\Server\bin\Release\net8.0-windows\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs 48 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 49 | 50 | [Icons] 51 | Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" 52 | Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon 53 | 54 | [Run] 55 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent 56 | 57 | [UninstallRun] 58 | Filename: "{cmd}"; Parameters: "/C ""taskkill /im ScreenTemperature.exe /f /t" -------------------------------------------------------------------------------- /Server/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Web.WebView2.Core; 3 | using ScreenTemperature.Services; 4 | using System.Windows; 5 | using System.Windows.Forms; 6 | 7 | 8 | namespace ScreenTemperature 9 | { 10 | public partial class MainWindow : Window 11 | { 12 | private readonly NotifyIcon _notifyIcon; 13 | 14 | #if DEBUG 15 | public string SourceUrl { get; set; } = "http://localhost:5173/"; 16 | #else 17 | public string SourceUrl { get; set; } = "http://localhost:61983/"; 18 | #endif 19 | 20 | public MainWindow() 21 | { 22 | InitializeComponent(); 23 | _ = InitializeWebView(); 24 | 25 | _notifyIcon = new NotifyIcon(); 26 | _notifyIcon.Icon = ScreenTemperature.Resources.icon; 27 | _notifyIcon.Visible = false; 28 | _notifyIcon.Text = "ScreenTemperature"; 29 | _notifyIcon.Click += NotifyIconOnClick; 30 | } 31 | 32 | private async Task InitializeWebView() 33 | { 34 | var env = await CoreWebView2Environment.CreateAsync(userDataFolder: App.DataFolder); 35 | var options = env.CreateCoreWebView2ControllerOptions(); 36 | options.IsInPrivateModeEnabled = true; 37 | 38 | await webView.EnsureCoreWebView2Async(env, options); 39 | webView.Source = new Uri(SourceUrl); 40 | } 41 | 42 | private async void Window_OnLoaded(object sender, RoutedEventArgs e) 43 | { 44 | // wait until server is built 45 | await Task.Run(async () => { 46 | while(WebApp.Instance.WebApplication == null) { await Task.Delay(25); } 47 | }); 48 | 49 | using(var scope = WebApp.Instance.WebApplication.Services.CreateScope()) 50 | { 51 | var parametersService = scope.ServiceProvider.GetRequiredService(); 52 | 53 | var parameters = await parametersService.GetParametersAsync(); 54 | 55 | if(parameters.MinimizeOnStartup) 56 | WindowState = WindowState.Minimized; 57 | } 58 | } 59 | 60 | ~MainWindow() 61 | { 62 | _notifyIcon.Click -= NotifyIconOnClick; 63 | _notifyIcon.Visible = false; 64 | _notifyIcon.Icon?.Dispose(); 65 | _notifyIcon.Dispose(); 66 | } 67 | 68 | private void NotifyIconOnClick(object? sender, EventArgs e) 69 | { 70 | _notifyIcon.Visible = false; 71 | Show(); 72 | Activate(); 73 | WindowState = WindowState.Normal; 74 | } 75 | 76 | private void Window_StateChanged(object sender, EventArgs e) 77 | { 78 | if (WindowState == WindowState.Minimized) 79 | { 80 | _notifyIcon.Visible = true; 81 | Hide(); 82 | } 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /Client/src/composables/useScreens.ts: -------------------------------------------------------------------------------- 1 | import { computed, ref } from "vue"; 2 | import { getScreens } from "@/global"; 3 | import { useQuery, UseQueryOptions } from "@tanstack/vue-query"; 4 | import { ScreenDto } from "@/dtos/screenDto"; 5 | 6 | export interface Screen { 7 | index: number; 8 | id: string; 9 | label: string; 10 | width: number; 11 | height: number; 12 | x: number; 13 | y: number; 14 | /** the width in percentages. */ 15 | widthPercentage: number; 16 | /** the height in percentages. */ 17 | heightPercentage: number; 18 | /** the X position in percentages. */ 19 | XPercentage: number; 20 | /** the Y position in percentages. */ 21 | YPercentage: number; 22 | isDDCSupported?: boolean; 23 | isBrightnessSupported?: boolean; 24 | brightnessMinimum?: number; 25 | brightnessMaximum?: number; 26 | currentBrightness?: number; 27 | } 28 | 29 | const selectedScreens = ref([]); 30 | 31 | export const useScreens = (options?: Partial>) => { 32 | 33 | const query = useQuery({ 34 | queryKey: ['screens'], 35 | queryFn: getScreens, 36 | staleTime: Infinity, 37 | refetchOnMount: false, 38 | ...options 39 | }); 40 | 41 | const aspectRatio = ref(""); 42 | 43 | const screens = computed(() => { 44 | 45 | let rectangleLeftBorderPosition = 0; 46 | let rectangleRightBorderPosition = 0; 47 | let rectangleTopBorderPosition = 0; 48 | let rectangleBottomBorderPosition = 0; 49 | 50 | let maximumX = 0; 51 | let maximumY = 0; 52 | 53 | for (const screen of (query.data.value ?? [])) { 54 | if (screen.x < rectangleLeftBorderPosition) rectangleLeftBorderPosition = screen.x; 55 | if (screen.x + screen.width > rectangleRightBorderPosition) rectangleRightBorderPosition = screen.x + screen.width; 56 | if (screen.y < rectangleTopBorderPosition) rectangleTopBorderPosition = screen.y; 57 | if (screen.y + screen.height > rectangleBottomBorderPosition) rectangleBottomBorderPosition = screen.y + screen.height; 58 | if (screen.x > maximumX) maximumX = screen.x; 59 | if (screen.y > maximumY) maximumY = screen.y; 60 | } 61 | 62 | const rectangleWidth = rectangleRightBorderPosition - rectangleLeftBorderPosition; 63 | const rectangleHeight = rectangleBottomBorderPosition - rectangleTopBorderPosition; 64 | 65 | aspectRatio.value = `${rectangleWidth} / ${rectangleHeight}`; 66 | 67 | return query.data.value?.map((x, index) => ({ 68 | index: index + 1, 69 | id: x.devicePath, 70 | label: x.label, 71 | width: x.width, 72 | height: x.height, 73 | x: x.x, 74 | y: x.y, 75 | widthPercentage: x.width * 100 / rectangleWidth, 76 | heightPercentage: x.height * 100 / rectangleHeight, 77 | XPercentage: (x.x - rectangleLeftBorderPosition) * (maximumX / rectangleWidth * 100) / maximumX, 78 | YPercentage: (x.y - rectangleTopBorderPosition) * (maximumY / rectangleHeight * 100) / maximumY, 79 | isDDCSupported: x.isDDCSupported, 80 | isBrightnessSupported: x.isBrightnessSupported, 81 | brightnessMinimum: x.minBrightness, 82 | brightnessMaximum: x.maxBrightness, 83 | currentBrightness: x.currentBrightness, 84 | } satisfies Screen)) ?? []; 85 | }); 86 | 87 | return { selectedScreens, screens, aspectRatio, ...query }; 88 | }; -------------------------------------------------------------------------------- /Server/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace ScreenTemperature { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ScreenTemperature.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 65 | /// 66 | internal static System.Drawing.Icon icon { 67 | get { 68 | object obj = ResourceManager.GetObject("icon", resourceCulture); 69 | return ((System.Drawing.Icon)(obj)); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Server/Migrations/20250209165047_multiple-configurations-per-binding.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | #nullable disable 5 | 6 | namespace ScreenTemperature.Migrations 7 | { 8 | /// 9 | public partial class multipleconfigurationsperbinding : Migration 10 | { 11 | /// 12 | protected override void Up(MigrationBuilder migrationBuilder) 13 | { 14 | migrationBuilder.DropForeignKey( 15 | name: "FK_KeyBindings_Configurations_ConfigurationId", 16 | table: "KeyBindings"); 17 | 18 | migrationBuilder.DropIndex( 19 | name: "IX_KeyBindings_ConfigurationId", 20 | table: "KeyBindings"); 21 | 22 | migrationBuilder.DropColumn( 23 | name: "ConfigurationId", 24 | table: "KeyBindings"); 25 | 26 | migrationBuilder.CreateTable( 27 | name: "ConfigurationKeyBinding", 28 | columns: table => new 29 | { 30 | ConfigurationsId = table.Column(type: "TEXT", nullable: false), 31 | KeyBindingsId = table.Column(type: "TEXT", nullable: false) 32 | }, 33 | constraints: table => 34 | { 35 | table.PrimaryKey("PK_ConfigurationKeyBinding", x => new { x.ConfigurationsId, x.KeyBindingsId }); 36 | table.ForeignKey( 37 | name: "FK_ConfigurationKeyBinding_Configurations_ConfigurationsId", 38 | column: x => x.ConfigurationsId, 39 | principalTable: "Configurations", 40 | principalColumn: "Id", 41 | onDelete: ReferentialAction.Cascade); 42 | table.ForeignKey( 43 | name: "FK_ConfigurationKeyBinding_KeyBindings_KeyBindingsId", 44 | column: x => x.KeyBindingsId, 45 | principalTable: "KeyBindings", 46 | principalColumn: "Id", 47 | onDelete: ReferentialAction.Cascade); 48 | }); 49 | 50 | migrationBuilder.CreateIndex( 51 | name: "IX_ConfigurationKeyBinding_KeyBindingsId", 52 | table: "ConfigurationKeyBinding", 53 | column: "KeyBindingsId"); 54 | } 55 | 56 | /// 57 | protected override void Down(MigrationBuilder migrationBuilder) 58 | { 59 | migrationBuilder.DropTable( 60 | name: "ConfigurationKeyBinding"); 61 | 62 | migrationBuilder.AddColumn( 63 | name: "ConfigurationId", 64 | table: "KeyBindings", 65 | type: "TEXT", 66 | nullable: false, 67 | defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); 68 | 69 | migrationBuilder.CreateIndex( 70 | name: "IX_KeyBindings_ConfigurationId", 71 | table: "KeyBindings", 72 | column: "ConfigurationId"); 73 | 74 | migrationBuilder.AddForeignKey( 75 | name: "FK_KeyBindings_Configurations_ConfigurationId", 76 | table: "KeyBindings", 77 | column: "ConfigurationId", 78 | principalTable: "Configurations", 79 | principalColumn: "Id", 80 | onDelete: ReferentialAction.Cascade); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Server/Services/ParametersService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.Logging; 3 | using Microsoft.Win32; 4 | using ScreenTemperature.DTOs; 5 | using ScreenTemperature.DTOs.Configurations; 6 | using ScreenTemperature.Entities; 7 | using ScreenTemperature.Entities.Configurations; 8 | using System.Diagnostics; 9 | using System.Reflection; 10 | using System.Text; 11 | 12 | namespace ScreenTemperature.Services; 13 | 14 | public interface IParametersService 15 | { 16 | Task GetParametersAsync(); 17 | Task UpdateParametersAsync(ParametersDto parameters); 18 | } 19 | 20 | public class ParametersService(ILogger logger, DatabaseContext databaseContext) : IParametersService 21 | { 22 | private const string _runKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Run"; 23 | private const string _minimizeOnStartupKey = "MinimizeOnStartup"; 24 | 25 | public async Task GetParametersAsync() 26 | { 27 | var startAppOnUserLogin = false; 28 | 29 | using (var key = Registry.CurrentUser.OpenSubKey(_runKeyPath, true)) 30 | { 31 | if (key != null) 32 | { 33 | var applicationName = Process.GetCurrentProcess().ProcessName; 34 | 35 | var subKey = key.GetValue(applicationName); 36 | 37 | startAppOnUserLogin = subKey != null; 38 | } 39 | } 40 | 41 | var minimizeOnStartupKeyValue = await databaseContext.Parameters.SingleOrDefaultAsync(x => x.Key == _minimizeOnStartupKey); 42 | 43 | return new ParametersDto() 44 | { 45 | StartApplicationOnUserLogin = startAppOnUserLogin, 46 | MinimizeOnStartup = minimizeOnStartupKeyValue != null ? bool.Parse(minimizeOnStartupKeyValue.Value) : false 47 | }; 48 | } 49 | 50 | public async Task UpdateParametersAsync(ParametersDto dto) 51 | { 52 | var currentParameters = await GetParametersAsync(); 53 | 54 | using (var key = Registry.CurrentUser.OpenSubKey(_runKeyPath, true)) 55 | { 56 | if (key != null) 57 | { 58 | var applicationName = Process.GetCurrentProcess().ProcessName; 59 | 60 | if (dto.StartApplicationOnUserLogin) 61 | { 62 | // Buffer to store the path 63 | var pathBuffer = new StringBuilder(260); // MAX_PATH = 260 64 | 65 | // Get the filename of the current module (pass IntPtr.Zero for current process) 66 | uint result = Win32.GetModuleFileName(IntPtr.Zero, pathBuffer, (uint)pathBuffer.Capacity); 67 | 68 | if (result > 0) 69 | key.SetValue(applicationName, pathBuffer.ToString()); 70 | } 71 | else 72 | { 73 | var subKey = key.GetValue(applicationName); 74 | 75 | if (subKey != null) 76 | { 77 | key.DeleteValue(applicationName); 78 | } 79 | } 80 | } 81 | } 82 | 83 | var minimizeOnStartupKeyValue = await databaseContext.Parameters.SingleOrDefaultAsync(x => x.Key == _minimizeOnStartupKey); 84 | 85 | if (minimizeOnStartupKeyValue == null) 86 | { 87 | minimizeOnStartupKeyValue = new Parameter() { Key = _minimizeOnStartupKey }; 88 | 89 | databaseContext.Parameters.Add(minimizeOnStartupKeyValue); 90 | } 91 | 92 | minimizeOnStartupKeyValue.Value = dto.MinimizeOnStartup.ToString(); 93 | 94 | await databaseContext.SaveChangesAsync(); 95 | 96 | return dto; 97 | } 98 | } -------------------------------------------------------------------------------- /Server/Controllers/KeyBindingController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.Extensions.Logging; 6 | using ScreenTemperature; 7 | using ScreenTemperature.DTOs; 8 | using ScreenTemperature.DTOs.Configurations; 9 | using ScreenTemperature.Entities; 10 | using ScreenTemperature.Mappers; 11 | using System.ComponentModel.DataAnnotations; 12 | 13 | public class KeyBindingController(ILogger logger, DatabaseContext databaseContext) 14 | { 15 | [HttpGet("/api/keybindings")] 16 | public async Task GetAllAsync(CancellationToken ct) 17 | { 18 | var bindings = await databaseContext.KeyBindings.Include(x => x.Configurations).ToListAsync(); 19 | 20 | return TypedResults.Ok(bindings.Select(x => x.ToDto())); 21 | } 22 | 23 | 24 | 25 | [HttpPut("/api/keybindings/{id:guid}")] 26 | public async Task CreateOrUpdateAsync([Required] Guid id, [FromBody][Required] KeyBindingDto dto, CancellationToken ct) 27 | { 28 | if (id != dto.Id) return TypedResults.BadRequest(new APIErrorResponseDto([$"{nameof(KeyBindingDto.Id)} mismatch."])); 29 | 30 | // todo : Add dto validation 31 | 32 | var shouldRegisterBinding = false; 33 | 34 | // Get entity in database or create a new one 35 | var entity = await databaseContext.KeyBindings.Include(x => x.Configurations).FirstOrDefaultAsync(x => x.Id == dto.Id, ct); 36 | 37 | // if it is an insertion 38 | if (entity == null) 39 | { 40 | entity = new KeyBinding() { Id = dto.Id }; 41 | databaseContext.KeyBindings.Add(entity); 42 | shouldRegisterBinding = true; 43 | } 44 | // if it is an update 45 | else 46 | { 47 | // if binding is updated 48 | if (entity.KeyCode != dto.KeyCode || entity.Alt != dto.Alt || entity.Control != dto.Control || !entity.Configurations.Select(x => x.Id).Order().SequenceEqual(dto.ConfigurationIds.Order())) 49 | { 50 | await HotKeyManager.Instance.UnregisterHotKeyAsync(entity.KeyCode, entity.Alt, entity.Control, false); 51 | shouldRegisterBinding = true; 52 | } 53 | } 54 | 55 | entity.Name = dto.Name; 56 | entity.KeyCode = dto.KeyCode; 57 | entity.Alt = dto.Alt; 58 | entity.Control = dto.Control; 59 | entity.Configurations = await databaseContext.Configurations.Where(x => dto.ConfigurationIds.Contains(x.Id)).ToListAsync(); 60 | 61 | await databaseContext.SaveChangesAsync(ct); 62 | 63 | var isHotKeyRegistered = true; 64 | 65 | if (shouldRegisterBinding) 66 | { 67 | isHotKeyRegistered = await HotKeyManager.Instance.RegisterHotKeyAsync(dto.KeyCode, dto.Alt, dto.Control, false); 68 | } 69 | 70 | return TypedResults.Ok(new KeyBindingWithHotKeyRegistrationResultDto() 71 | { 72 | IsHotKeyRegistered = isHotKeyRegistered, 73 | KeyBinding = entity.ToDto() 74 | }); 75 | } 76 | 77 | 78 | 79 | [HttpDelete("/api/keybindings/{id:guid}")] 80 | public async Task DeleteAsync([Required] Guid id, CancellationToken ct) 81 | { 82 | var entity = await databaseContext.KeyBindings.SingleOrDefaultAsync(x => x.Id == id, cancellationToken: ct); 83 | 84 | if (entity == null) return TypedResults.BadRequest("This key binding does not exist."); 85 | 86 | databaseContext.Remove(entity); 87 | 88 | await databaseContext.SaveChangesAsync(ct); 89 | 90 | await HotKeyManager.Instance.UnregisterHotKeyAsync(entity.KeyCode, entity.Alt, entity.Control, false); 91 | 92 | return TypedResults.Ok(); 93 | } 94 | } -------------------------------------------------------------------------------- /Client/src/components/Pages/ParametersPage.vue: -------------------------------------------------------------------------------- 1 | 78 | 79 | 124 | 125 | -------------------------------------------------------------------------------- /Client/src/global.ts: -------------------------------------------------------------------------------- 1 | import { ConfigurationDto } from "./dtos/configurations/configurationDto"; 2 | import { KeyBindingDto } from "./dtos/keyBindingDto"; 3 | import { ParametersDto } from "./dtos/parameters"; 4 | import { ScreenDto } from "./dtos/screenDto"; 5 | 6 | export enum Routes { 7 | CATEGORY_SELECTION = "CATEGORY_SELECTION", 8 | CONFIGURATIONS = "CONFIGURATIONS", 9 | CONFIGURATIONS_CREATE = "CONFIGURATIONS_CREATE", 10 | KEY_BINDINGS = "KEY_BINDINGS", 11 | PARAMETERS = "PARAMETERS", 12 | CONFIGURATIONS_UPDATE = "CONFIGURATIONS_UPDATE", 13 | KEY_BINDING_UPDATE = "KEY_BINDING_UPDATE", 14 | KEY_BINDING_CREATE = "KEY_BINDING_CREATE", 15 | } 16 | 17 | export const getScreens = async () : Promise => { 18 | const response = await fetch(`${import.meta.env.VITE_SERVER_BASE_URL}/api/screens`); 19 | 20 | if(response.status == 200) 21 | return await response.json(); 22 | 23 | throw Error(); 24 | }; 25 | 26 | export const getConfigurations = async () : Promise => { 27 | const response = await fetch(`${import.meta.env.VITE_SERVER_BASE_URL}/api/configurations`); 28 | 29 | if(response.status == 200) 30 | return await response.json(); 31 | 32 | throw Error(); 33 | }; 34 | 35 | export const saveConfiguration = async (configuration: ConfigurationDto) : Promise => { 36 | const response = await fetch(`${import.meta.env.VITE_SERVER_BASE_URL}/api/configurations/${configuration.id}`, { 37 | method: "PUT", 38 | headers: { 39 | "Content-Type": "application/json", 40 | }, 41 | body: JSON.stringify(configuration) 42 | }); 43 | 44 | if(response.status == 200) 45 | return await response.json(); 46 | 47 | throw Error(); 48 | }; 49 | 50 | export const deleteConfiguration = async (configuration: ConfigurationDto) : Promise => { 51 | const response = await fetch(`${import.meta.env.VITE_SERVER_BASE_URL}/api/configurations/${configuration.id}`, { 52 | method: "DELETE", 53 | }); 54 | 55 | if(response.status == 200) 56 | return; 57 | 58 | throw Error(); 59 | }; 60 | 61 | export const getKeyBindings = async () : Promise => { 62 | const response = await fetch(`${import.meta.env.VITE_SERVER_BASE_URL}/api/keybindings`); 63 | 64 | if(response.status == 200) 65 | return await response.json(); 66 | 67 | throw Error(); 68 | }; 69 | 70 | export const saveKeyBinding = async (binding: KeyBindingDto) : Promise => { 71 | const response = await fetch(`${import.meta.env.VITE_SERVER_BASE_URL}/api/keybindings/${binding.id}`, { 72 | method: "PUT", 73 | headers: { 74 | "Content-Type": "application/json", 75 | }, 76 | body: JSON.stringify(binding) 77 | }); 78 | 79 | if(response.status == 200) 80 | return await response.json(); 81 | 82 | throw Error(); 83 | }; 84 | 85 | export const deleteKeyBinding = async (binding: KeyBindingDto) : Promise => { 86 | const response = await fetch(`${import.meta.env.VITE_SERVER_BASE_URL}/api/keybindings/${binding.id}`, { 87 | method: "DELETE", 88 | }); 89 | 90 | if(response.status == 200) 91 | return; 92 | 93 | throw Error(); 94 | }; 95 | 96 | export const getParameters = async () : Promise => { 97 | const response = await fetch(`${import.meta.env.VITE_SERVER_BASE_URL}/api/parameters`); 98 | 99 | if(response.status == 200) 100 | return await response.json(); 101 | 102 | throw Error(); 103 | }; 104 | 105 | export const updateParameters = async (parameters: ParametersDto) : Promise => { 106 | const response = await fetch(`${import.meta.env.VITE_SERVER_BASE_URL}/api/parameters`, { 107 | method: "PUT", 108 | headers: { 109 | "Content-Type": "application/json", 110 | }, 111 | body: JSON.stringify(parameters) 112 | }); 113 | 114 | if(response.status == 200) 115 | return await response.json(); 116 | 117 | throw Error(); 118 | }; 119 | 120 | export const isNullOrWhitespace = ( input?: string ) => { 121 | return (typeof input === 'undefined' || input == null) 122 | || input.replace(/\s/g, '').length < 1; 123 | }; -------------------------------------------------------------------------------- /Server/Controllers/ConfigurationController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Http.HttpResults; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.Extensions.Logging; 7 | using ScreenTemperature; 8 | using ScreenTemperature.DTOs; 9 | using ScreenTemperature.DTOs.Configurations; 10 | using ScreenTemperature.Entities.Configurations; 11 | using ScreenTemperature.Mappers; 12 | using ScreenTemperature.Services; 13 | using System.ComponentModel.DataAnnotations; 14 | 15 | public class ConfigurationController(ILogger logger, DatabaseContext databaseContext, IScreenService screenService) 16 | { 17 | [HttpGet("/api/configurations")] 18 | public async Task GetAllAsync() 19 | { 20 | var entities = await databaseContext.Configurations.ToListAsync(); 21 | 22 | return TypedResults.Ok(entities.Select(x => x.ToDto())); 23 | } 24 | 25 | 26 | 27 | [HttpPut("/api/configurations/{id:guid}")] 28 | public async Task CreateOrUpdateAsync([Required] Guid id, [FromBody][Required] ConfigurationDto dto) 29 | { 30 | if(id != dto.Id) return TypedResults.BadRequest(new APIErrorResponseDto([$"{nameof(ConfigurationDto.Id)} mismatch."])); 31 | 32 | // todo : Add dto validation 33 | 34 | var entity = await databaseContext.Configurations.FirstOrDefaultAsync(x => x.Id == dto.Id); 35 | 36 | if (entity == null) 37 | { 38 | if (dto is TemperatureConfigurationDto) 39 | entity = new TemperatureConfiguration() { Id = dto.Id }; 40 | else if (dto is ColorConfigurationDto) 41 | entity = new ColorConfiguration() { Id = dto.Id }; 42 | else 43 | throw new NotImplementedException(); 44 | 45 | databaseContext.Configurations.Add(entity); 46 | } 47 | 48 | entity.Name = dto.Name; 49 | entity.DevicePath = dto.DevicePath; 50 | entity.ApplyBrightness = dto.ApplyBrightness; 51 | entity.Brightness = dto.Brightness; 52 | entity.ApplyAtStartup = dto.ApplyAtStartup; 53 | 54 | if (entity is ColorConfiguration colorConfiguration && dto is ColorConfigurationDto colorConfigurationDto) 55 | { 56 | colorConfiguration.ApplyColor = colorConfigurationDto.ApplyColor; 57 | colorConfiguration.Color = colorConfigurationDto.Color; 58 | } 59 | else if (entity is TemperatureConfiguration temperatureConfiguration && dto is TemperatureConfigurationDto temperatureConfigurationDto) 60 | { 61 | temperatureConfiguration.ApplyIntensity = temperatureConfigurationDto.ApplyIntensity; 62 | temperatureConfiguration.Intensity = temperatureConfigurationDto.Intensity; 63 | } 64 | else 65 | { 66 | return TypedResults.BadRequest(new APIErrorResponseDto([$"Type of configuration {dto.Id} is invalid."])); 67 | } 68 | 69 | await databaseContext.SaveChangesAsync(); 70 | 71 | return TypedResults.Ok(entity.ToDto()); 72 | } 73 | 74 | 75 | 76 | [HttpDelete("/api/configurations/{id:guid}")] 77 | public async Task DeleteAsync([Required] Guid id) 78 | { 79 | var entity = await databaseContext.Configurations.SingleOrDefaultAsync(x => x.Id == id); 80 | 81 | if (entity == null) return TypedResults.BadRequest(new APIErrorResponseDto(["This configuration does not exist."])); 82 | 83 | databaseContext.Configurations.Remove(entity); 84 | 85 | await databaseContext.SaveChangesAsync(); 86 | 87 | return TypedResults.Ok(); 88 | } 89 | 90 | 91 | 92 | //[HttpPost("/api/configurations/apply")] 93 | //public async Task ApplyAsync([FromBody][Required] ConfigurationDto dto) 94 | //{ 95 | // if (dto.ApplyBrightness) 96 | // _screenService.ApplyBrightnessToScreenAsync(dto.Brightness, dto.DevicePath); 97 | 98 | // if (dto is TemperatureConfigurationDto temperatureConfiguration) 99 | // { 100 | // if (temperatureConfiguration.ApplyIntensity) 101 | // _screenService.ApplyKelvinToScreenAsync(temperatureConfiguration.Intensity, temperatureConfiguration.DevicePath); 102 | // } 103 | // else if (dto is ColorConfigurationDto colorConfiguration) 104 | // { 105 | // if (colorConfiguration.ApplyColor) 106 | // _screenService.ApplyColorToScreenAsync(colorConfiguration.Color, colorConfiguration.DevicePath); 107 | // } 108 | // else 109 | // { 110 | // throw new NotImplementedException(); 111 | // } 112 | 113 | // return TypedResults.Ok(); 114 | //} 115 | } -------------------------------------------------------------------------------- /Server/Migrations/20250209150504_remove-commands.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | #nullable disable 5 | 6 | namespace ScreenTemperature.Migrations 7 | { 8 | /// 9 | public partial class removecommands : Migration 10 | { 11 | /// 12 | protected override void Up(MigrationBuilder migrationBuilder) 13 | { 14 | migrationBuilder.DropTable( 15 | name: "ApplyConfigurationCommands"); 16 | 17 | migrationBuilder.DropTable( 18 | name: "Commands"); 19 | 20 | migrationBuilder.DropColumn( 21 | name: "Shift", 22 | table: "KeyBindings"); 23 | 24 | migrationBuilder.AddColumn( 25 | name: "ConfigurationId", 26 | table: "KeyBindings", 27 | type: "TEXT", 28 | nullable: false, 29 | defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); 30 | 31 | migrationBuilder.CreateIndex( 32 | name: "IX_KeyBindings_ConfigurationId", 33 | table: "KeyBindings", 34 | column: "ConfigurationId"); 35 | 36 | migrationBuilder.AddForeignKey( 37 | name: "FK_KeyBindings_Configurations_ConfigurationId", 38 | table: "KeyBindings", 39 | column: "ConfigurationId", 40 | principalTable: "Configurations", 41 | principalColumn: "Id", 42 | onDelete: ReferentialAction.Cascade); 43 | } 44 | 45 | /// 46 | protected override void Down(MigrationBuilder migrationBuilder) 47 | { 48 | migrationBuilder.DropForeignKey( 49 | name: "FK_KeyBindings_Configurations_ConfigurationId", 50 | table: "KeyBindings"); 51 | 52 | migrationBuilder.DropIndex( 53 | name: "IX_KeyBindings_ConfigurationId", 54 | table: "KeyBindings"); 55 | 56 | migrationBuilder.DropColumn( 57 | name: "ConfigurationId", 58 | table: "KeyBindings"); 59 | 60 | migrationBuilder.AddColumn( 61 | name: "Shift", 62 | table: "KeyBindings", 63 | type: "INTEGER", 64 | nullable: false, 65 | defaultValue: false); 66 | 67 | migrationBuilder.CreateTable( 68 | name: "Commands", 69 | columns: table => new 70 | { 71 | Id = table.Column(type: "TEXT", nullable: false), 72 | KeyBindingId = table.Column(type: "TEXT", nullable: false) 73 | }, 74 | constraints: table => 75 | { 76 | table.PrimaryKey("PK_Commands", x => x.Id); 77 | table.ForeignKey( 78 | name: "FK_Commands_KeyBindings_KeyBindingId", 79 | column: x => x.KeyBindingId, 80 | principalTable: "KeyBindings", 81 | principalColumn: "Id", 82 | onDelete: ReferentialAction.Cascade); 83 | }); 84 | 85 | migrationBuilder.CreateTable( 86 | name: "ApplyConfigurationCommands", 87 | columns: table => new 88 | { 89 | Id = table.Column(type: "TEXT", nullable: false), 90 | ConfigurationId = table.Column(type: "TEXT", nullable: false) 91 | }, 92 | constraints: table => 93 | { 94 | table.PrimaryKey("PK_ApplyConfigurationCommands", x => x.Id); 95 | table.ForeignKey( 96 | name: "FK_ApplyConfigurationCommands_Commands_Id", 97 | column: x => x.Id, 98 | principalTable: "Commands", 99 | principalColumn: "Id", 100 | onDelete: ReferentialAction.Cascade); 101 | table.ForeignKey( 102 | name: "FK_ApplyConfigurationCommands_Configurations_ConfigurationId", 103 | column: x => x.ConfigurationId, 104 | principalTable: "Configurations", 105 | principalColumn: "Id", 106 | onDelete: ReferentialAction.Cascade); 107 | }); 108 | 109 | migrationBuilder.CreateIndex( 110 | name: "IX_ApplyConfigurationCommands_ConfigurationId", 111 | table: "ApplyConfigurationCommands", 112 | column: "ConfigurationId"); 113 | 114 | migrationBuilder.CreateIndex( 115 | name: "IX_Commands_KeyBindingId", 116 | table: "Commands", 117 | column: "KeyBindingId"); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Server/Migrations/20250209150504_remove-commands.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | using ScreenTemperature; 8 | 9 | #nullable disable 10 | 11 | namespace ScreenTemperature.Migrations 12 | { 13 | [DbContext(typeof(DatabaseContext))] 14 | [Migration("20250209150504_remove-commands")] 15 | partial class removecommands 16 | { 17 | /// 18 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 19 | { 20 | #pragma warning disable 612, 618 21 | modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); 22 | 23 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.Configuration", b => 24 | { 25 | b.Property("Id") 26 | .HasColumnType("TEXT"); 27 | 28 | b.Property("ApplyBrightness") 29 | .HasColumnType("INTEGER"); 30 | 31 | b.Property("Brightness") 32 | .HasColumnType("INTEGER"); 33 | 34 | b.Property("DevicePath") 35 | .IsRequired() 36 | .HasColumnType("TEXT"); 37 | 38 | b.Property("Name") 39 | .IsRequired() 40 | .HasColumnType("TEXT"); 41 | 42 | b.HasKey("Id"); 43 | 44 | b.ToTable("Configurations"); 45 | 46 | b.UseTptMappingStrategy(); 47 | }); 48 | 49 | modelBuilder.Entity("ScreenTemperature.Entities.KeyBinding", b => 50 | { 51 | b.Property("Id") 52 | .HasColumnType("TEXT"); 53 | 54 | b.Property("Alt") 55 | .HasColumnType("INTEGER"); 56 | 57 | b.Property("ConfigurationId") 58 | .HasColumnType("TEXT"); 59 | 60 | b.Property("Control") 61 | .HasColumnType("INTEGER"); 62 | 63 | b.Property("KeyCode") 64 | .HasColumnType("INTEGER"); 65 | 66 | b.Property("Name") 67 | .IsRequired() 68 | .HasColumnType("TEXT"); 69 | 70 | b.HasKey("Id"); 71 | 72 | b.HasIndex("ConfigurationId"); 73 | 74 | b.ToTable("KeyBindings"); 75 | }); 76 | 77 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.ColorConfiguration", b => 78 | { 79 | b.HasBaseType("ScreenTemperature.Entities.Configurations.Configuration"); 80 | 81 | b.Property("ApplyColor") 82 | .HasColumnType("INTEGER"); 83 | 84 | b.Property("Color") 85 | .IsRequired() 86 | .HasColumnType("TEXT"); 87 | 88 | b.ToTable("ColorConfigurations"); 89 | }); 90 | 91 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", b => 92 | { 93 | b.HasBaseType("ScreenTemperature.Entities.Configurations.Configuration"); 94 | 95 | b.Property("ApplyIntensity") 96 | .HasColumnType("INTEGER"); 97 | 98 | b.Property("Intensity") 99 | .HasColumnType("INTEGER"); 100 | 101 | b.ToTable("TemperatureConfigurations"); 102 | }); 103 | 104 | modelBuilder.Entity("ScreenTemperature.Entities.KeyBinding", b => 105 | { 106 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", "Configuration") 107 | .WithMany() 108 | .HasForeignKey("ConfigurationId") 109 | .OnDelete(DeleteBehavior.Cascade) 110 | .IsRequired(); 111 | 112 | b.Navigation("Configuration"); 113 | }); 114 | 115 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.ColorConfiguration", b => 116 | { 117 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 118 | .WithOne() 119 | .HasForeignKey("ScreenTemperature.Entities.Configurations.ColorConfiguration", "Id") 120 | .OnDelete(DeleteBehavior.Cascade) 121 | .IsRequired(); 122 | }); 123 | 124 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", b => 125 | { 126 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 127 | .WithOne() 128 | .HasForeignKey("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", "Id") 129 | .OnDelete(DeleteBehavior.Cascade) 130 | .IsRequired(); 131 | }); 132 | #pragma warning restore 612, 618 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Server/Migrations/20250209165047_multiple-configurations-per-binding.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | using ScreenTemperature; 8 | 9 | #nullable disable 10 | 11 | namespace ScreenTemperature.Migrations 12 | { 13 | [DbContext(typeof(DatabaseContext))] 14 | [Migration("20250209165047_multiple-configurations-per-binding")] 15 | partial class multipleconfigurationsperbinding 16 | { 17 | /// 18 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 19 | { 20 | #pragma warning disable 612, 618 21 | modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); 22 | 23 | modelBuilder.Entity("ConfigurationKeyBinding", b => 24 | { 25 | b.Property("ConfigurationsId") 26 | .HasColumnType("TEXT"); 27 | 28 | b.Property("KeyBindingsId") 29 | .HasColumnType("TEXT"); 30 | 31 | b.HasKey("ConfigurationsId", "KeyBindingsId"); 32 | 33 | b.HasIndex("KeyBindingsId"); 34 | 35 | b.ToTable("ConfigurationKeyBinding"); 36 | }); 37 | 38 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.Configuration", b => 39 | { 40 | b.Property("Id") 41 | .HasColumnType("TEXT"); 42 | 43 | b.Property("ApplyBrightness") 44 | .HasColumnType("INTEGER"); 45 | 46 | b.Property("Brightness") 47 | .HasColumnType("INTEGER"); 48 | 49 | b.Property("DevicePath") 50 | .IsRequired() 51 | .HasColumnType("TEXT"); 52 | 53 | b.Property("Name") 54 | .IsRequired() 55 | .HasColumnType("TEXT"); 56 | 57 | b.HasKey("Id"); 58 | 59 | b.ToTable("Configurations"); 60 | 61 | b.UseTptMappingStrategy(); 62 | }); 63 | 64 | modelBuilder.Entity("ScreenTemperature.Entities.KeyBinding", b => 65 | { 66 | b.Property("Id") 67 | .HasColumnType("TEXT"); 68 | 69 | b.Property("Alt") 70 | .HasColumnType("INTEGER"); 71 | 72 | b.Property("Control") 73 | .HasColumnType("INTEGER"); 74 | 75 | b.Property("KeyCode") 76 | .HasColumnType("INTEGER"); 77 | 78 | b.Property("Name") 79 | .IsRequired() 80 | .HasColumnType("TEXT"); 81 | 82 | b.HasKey("Id"); 83 | 84 | b.ToTable("KeyBindings"); 85 | }); 86 | 87 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.ColorConfiguration", b => 88 | { 89 | b.HasBaseType("ScreenTemperature.Entities.Configurations.Configuration"); 90 | 91 | b.Property("ApplyColor") 92 | .HasColumnType("INTEGER"); 93 | 94 | b.Property("Color") 95 | .IsRequired() 96 | .HasColumnType("TEXT"); 97 | 98 | b.ToTable("ColorConfigurations"); 99 | }); 100 | 101 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", b => 102 | { 103 | b.HasBaseType("ScreenTemperature.Entities.Configurations.Configuration"); 104 | 105 | b.Property("ApplyIntensity") 106 | .HasColumnType("INTEGER"); 107 | 108 | b.Property("Intensity") 109 | .HasColumnType("INTEGER"); 110 | 111 | b.ToTable("TemperatureConfigurations"); 112 | }); 113 | 114 | modelBuilder.Entity("ConfigurationKeyBinding", b => 115 | { 116 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 117 | .WithMany() 118 | .HasForeignKey("ConfigurationsId") 119 | .OnDelete(DeleteBehavior.Cascade) 120 | .IsRequired(); 121 | 122 | b.HasOne("ScreenTemperature.Entities.KeyBinding", null) 123 | .WithMany() 124 | .HasForeignKey("KeyBindingsId") 125 | .OnDelete(DeleteBehavior.Cascade) 126 | .IsRequired(); 127 | }); 128 | 129 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.ColorConfiguration", b => 130 | { 131 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 132 | .WithOne() 133 | .HasForeignKey("ScreenTemperature.Entities.Configurations.ColorConfiguration", "Id") 134 | .OnDelete(DeleteBehavior.Cascade) 135 | .IsRequired(); 136 | }); 137 | 138 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", b => 139 | { 140 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 141 | .WithOne() 142 | .HasForeignKey("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", "Id") 143 | .OnDelete(DeleteBehavior.Cascade) 144 | .IsRequired(); 145 | }); 146 | #pragma warning restore 612, 618 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Server/Migrations/20250215165711_apply-configuration-at-startup.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | using ScreenTemperature; 8 | 9 | #nullable disable 10 | 11 | namespace ScreenTemperature.Migrations 12 | { 13 | [DbContext(typeof(DatabaseContext))] 14 | [Migration("20250215165711_apply-configuration-at-startup")] 15 | partial class applyconfigurationatstartup 16 | { 17 | /// 18 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 19 | { 20 | #pragma warning disable 612, 618 21 | modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); 22 | 23 | modelBuilder.Entity("ConfigurationKeyBinding", b => 24 | { 25 | b.Property("ConfigurationsId") 26 | .HasColumnType("TEXT"); 27 | 28 | b.Property("KeyBindingsId") 29 | .HasColumnType("TEXT"); 30 | 31 | b.HasKey("ConfigurationsId", "KeyBindingsId"); 32 | 33 | b.HasIndex("KeyBindingsId"); 34 | 35 | b.ToTable("ConfigurationKeyBinding"); 36 | }); 37 | 38 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.Configuration", b => 39 | { 40 | b.Property("Id") 41 | .HasColumnType("TEXT"); 42 | 43 | b.Property("ApplyAtStartup") 44 | .HasColumnType("INTEGER"); 45 | 46 | b.Property("ApplyBrightness") 47 | .HasColumnType("INTEGER"); 48 | 49 | b.Property("Brightness") 50 | .HasColumnType("INTEGER"); 51 | 52 | b.Property("DevicePath") 53 | .IsRequired() 54 | .HasColumnType("TEXT"); 55 | 56 | b.Property("Name") 57 | .IsRequired() 58 | .HasColumnType("TEXT"); 59 | 60 | b.HasKey("Id"); 61 | 62 | b.ToTable("Configurations"); 63 | 64 | b.UseTptMappingStrategy(); 65 | }); 66 | 67 | modelBuilder.Entity("ScreenTemperature.Entities.KeyBinding", b => 68 | { 69 | b.Property("Id") 70 | .HasColumnType("TEXT"); 71 | 72 | b.Property("Alt") 73 | .HasColumnType("INTEGER"); 74 | 75 | b.Property("Control") 76 | .HasColumnType("INTEGER"); 77 | 78 | b.Property("KeyCode") 79 | .HasColumnType("INTEGER"); 80 | 81 | b.Property("Name") 82 | .IsRequired() 83 | .HasColumnType("TEXT"); 84 | 85 | b.HasKey("Id"); 86 | 87 | b.ToTable("KeyBindings"); 88 | }); 89 | 90 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.ColorConfiguration", b => 91 | { 92 | b.HasBaseType("ScreenTemperature.Entities.Configurations.Configuration"); 93 | 94 | b.Property("ApplyColor") 95 | .HasColumnType("INTEGER"); 96 | 97 | b.Property("Color") 98 | .IsRequired() 99 | .HasColumnType("TEXT"); 100 | 101 | b.ToTable("ColorConfigurations"); 102 | }); 103 | 104 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", b => 105 | { 106 | b.HasBaseType("ScreenTemperature.Entities.Configurations.Configuration"); 107 | 108 | b.Property("ApplyIntensity") 109 | .HasColumnType("INTEGER"); 110 | 111 | b.Property("Intensity") 112 | .HasColumnType("INTEGER"); 113 | 114 | b.ToTable("TemperatureConfigurations"); 115 | }); 116 | 117 | modelBuilder.Entity("ConfigurationKeyBinding", b => 118 | { 119 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 120 | .WithMany() 121 | .HasForeignKey("ConfigurationsId") 122 | .OnDelete(DeleteBehavior.Cascade) 123 | .IsRequired(); 124 | 125 | b.HasOne("ScreenTemperature.Entities.KeyBinding", null) 126 | .WithMany() 127 | .HasForeignKey("KeyBindingsId") 128 | .OnDelete(DeleteBehavior.Cascade) 129 | .IsRequired(); 130 | }); 131 | 132 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.ColorConfiguration", b => 133 | { 134 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 135 | .WithOne() 136 | .HasForeignKey("ScreenTemperature.Entities.Configurations.ColorConfiguration", "Id") 137 | .OnDelete(DeleteBehavior.Cascade) 138 | .IsRequired(); 139 | }); 140 | 141 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", b => 142 | { 143 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 144 | .WithOne() 145 | .HasForeignKey("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", "Id") 146 | .OnDelete(DeleteBehavior.Cascade) 147 | .IsRequired(); 148 | }); 149 | #pragma warning restore 612, 618 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Server/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | Resources\icon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | -------------------------------------------------------------------------------- /Server/Migrations/DatabaseContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 6 | using ScreenTemperature; 7 | 8 | #nullable disable 9 | 10 | namespace ScreenTemperature.Migrations 11 | { 12 | [DbContext(typeof(DatabaseContext))] 13 | partial class DatabaseContextModelSnapshot : ModelSnapshot 14 | { 15 | protected override void BuildModel(ModelBuilder modelBuilder) 16 | { 17 | #pragma warning disable 612, 618 18 | modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); 19 | 20 | modelBuilder.Entity("ConfigurationKeyBinding", b => 21 | { 22 | b.Property("ConfigurationsId") 23 | .HasColumnType("TEXT"); 24 | 25 | b.Property("KeyBindingsId") 26 | .HasColumnType("TEXT"); 27 | 28 | b.HasKey("ConfigurationsId", "KeyBindingsId"); 29 | 30 | b.HasIndex("KeyBindingsId"); 31 | 32 | b.ToTable("ConfigurationKeyBinding"); 33 | }); 34 | 35 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.Configuration", b => 36 | { 37 | b.Property("Id") 38 | .HasColumnType("TEXT"); 39 | 40 | b.Property("ApplyAtStartup") 41 | .HasColumnType("INTEGER"); 42 | 43 | b.Property("ApplyBrightness") 44 | .HasColumnType("INTEGER"); 45 | 46 | b.Property("Brightness") 47 | .HasColumnType("INTEGER"); 48 | 49 | b.Property("DevicePath") 50 | .IsRequired() 51 | .HasColumnType("TEXT"); 52 | 53 | b.Property("Name") 54 | .IsRequired() 55 | .HasColumnType("TEXT"); 56 | 57 | b.HasKey("Id"); 58 | 59 | b.ToTable("Configurations"); 60 | 61 | b.UseTptMappingStrategy(); 62 | }); 63 | 64 | modelBuilder.Entity("ScreenTemperature.Entities.KeyBinding", b => 65 | { 66 | b.Property("Id") 67 | .HasColumnType("TEXT"); 68 | 69 | b.Property("Alt") 70 | .HasColumnType("INTEGER"); 71 | 72 | b.Property("Control") 73 | .HasColumnType("INTEGER"); 74 | 75 | b.Property("KeyCode") 76 | .HasColumnType("INTEGER"); 77 | 78 | b.Property("Name") 79 | .IsRequired() 80 | .HasColumnType("TEXT"); 81 | 82 | b.HasKey("Id"); 83 | 84 | b.ToTable("KeyBindings"); 85 | }); 86 | 87 | modelBuilder.Entity("ScreenTemperature.Entities.Parameter", b => 88 | { 89 | b.Property("Key") 90 | .HasColumnType("TEXT"); 91 | 92 | b.Property("Value") 93 | .IsRequired() 94 | .HasColumnType("TEXT"); 95 | 96 | b.HasKey("Key"); 97 | 98 | b.ToTable("Parameters"); 99 | }); 100 | 101 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.ColorConfiguration", b => 102 | { 103 | b.HasBaseType("ScreenTemperature.Entities.Configurations.Configuration"); 104 | 105 | b.Property("ApplyColor") 106 | .HasColumnType("INTEGER"); 107 | 108 | b.Property("Color") 109 | .IsRequired() 110 | .HasColumnType("TEXT"); 111 | 112 | b.ToTable("ColorConfigurations"); 113 | }); 114 | 115 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", b => 116 | { 117 | b.HasBaseType("ScreenTemperature.Entities.Configurations.Configuration"); 118 | 119 | b.Property("ApplyIntensity") 120 | .HasColumnType("INTEGER"); 121 | 122 | b.Property("Intensity") 123 | .HasColumnType("INTEGER"); 124 | 125 | b.ToTable("TemperatureConfigurations"); 126 | }); 127 | 128 | modelBuilder.Entity("ConfigurationKeyBinding", b => 129 | { 130 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 131 | .WithMany() 132 | .HasForeignKey("ConfigurationsId") 133 | .OnDelete(DeleteBehavior.Cascade) 134 | .IsRequired(); 135 | 136 | b.HasOne("ScreenTemperature.Entities.KeyBinding", null) 137 | .WithMany() 138 | .HasForeignKey("KeyBindingsId") 139 | .OnDelete(DeleteBehavior.Cascade) 140 | .IsRequired(); 141 | }); 142 | 143 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.ColorConfiguration", b => 144 | { 145 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 146 | .WithOne() 147 | .HasForeignKey("ScreenTemperature.Entities.Configurations.ColorConfiguration", "Id") 148 | .OnDelete(DeleteBehavior.Cascade) 149 | .IsRequired(); 150 | }); 151 | 152 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", b => 153 | { 154 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 155 | .WithOne() 156 | .HasForeignKey("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", "Id") 157 | .OnDelete(DeleteBehavior.Cascade) 158 | .IsRequired(); 159 | }); 160 | #pragma warning restore 612, 618 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Server/Migrations/20250215192516_minimize-on-startup-parameter.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | using ScreenTemperature; 8 | 9 | #nullable disable 10 | 11 | namespace ScreenTemperature.Migrations 12 | { 13 | [DbContext(typeof(DatabaseContext))] 14 | [Migration("20250215192516_minimize-on-startup-parameter")] 15 | partial class minimizeonstartupparameter 16 | { 17 | /// 18 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 19 | { 20 | #pragma warning disable 612, 618 21 | modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); 22 | 23 | modelBuilder.Entity("ConfigurationKeyBinding", b => 24 | { 25 | b.Property("ConfigurationsId") 26 | .HasColumnType("TEXT"); 27 | 28 | b.Property("KeyBindingsId") 29 | .HasColumnType("TEXT"); 30 | 31 | b.HasKey("ConfigurationsId", "KeyBindingsId"); 32 | 33 | b.HasIndex("KeyBindingsId"); 34 | 35 | b.ToTable("ConfigurationKeyBinding"); 36 | }); 37 | 38 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.Configuration", b => 39 | { 40 | b.Property("Id") 41 | .HasColumnType("TEXT"); 42 | 43 | b.Property("ApplyAtStartup") 44 | .HasColumnType("INTEGER"); 45 | 46 | b.Property("ApplyBrightness") 47 | .HasColumnType("INTEGER"); 48 | 49 | b.Property("Brightness") 50 | .HasColumnType("INTEGER"); 51 | 52 | b.Property("DevicePath") 53 | .IsRequired() 54 | .HasColumnType("TEXT"); 55 | 56 | b.Property("Name") 57 | .IsRequired() 58 | .HasColumnType("TEXT"); 59 | 60 | b.HasKey("Id"); 61 | 62 | b.ToTable("Configurations"); 63 | 64 | b.UseTptMappingStrategy(); 65 | }); 66 | 67 | modelBuilder.Entity("ScreenTemperature.Entities.KeyBinding", b => 68 | { 69 | b.Property("Id") 70 | .HasColumnType("TEXT"); 71 | 72 | b.Property("Alt") 73 | .HasColumnType("INTEGER"); 74 | 75 | b.Property("Control") 76 | .HasColumnType("INTEGER"); 77 | 78 | b.Property("KeyCode") 79 | .HasColumnType("INTEGER"); 80 | 81 | b.Property("Name") 82 | .IsRequired() 83 | .HasColumnType("TEXT"); 84 | 85 | b.HasKey("Id"); 86 | 87 | b.ToTable("KeyBindings"); 88 | }); 89 | 90 | modelBuilder.Entity("ScreenTemperature.Entities.Parameter", b => 91 | { 92 | b.Property("Key") 93 | .HasColumnType("TEXT"); 94 | 95 | b.Property("Value") 96 | .IsRequired() 97 | .HasColumnType("TEXT"); 98 | 99 | b.HasKey("Key"); 100 | 101 | b.ToTable("Parameters"); 102 | }); 103 | 104 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.ColorConfiguration", b => 105 | { 106 | b.HasBaseType("ScreenTemperature.Entities.Configurations.Configuration"); 107 | 108 | b.Property("ApplyColor") 109 | .HasColumnType("INTEGER"); 110 | 111 | b.Property("Color") 112 | .IsRequired() 113 | .HasColumnType("TEXT"); 114 | 115 | b.ToTable("ColorConfigurations"); 116 | }); 117 | 118 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", b => 119 | { 120 | b.HasBaseType("ScreenTemperature.Entities.Configurations.Configuration"); 121 | 122 | b.Property("ApplyIntensity") 123 | .HasColumnType("INTEGER"); 124 | 125 | b.Property("Intensity") 126 | .HasColumnType("INTEGER"); 127 | 128 | b.ToTable("TemperatureConfigurations"); 129 | }); 130 | 131 | modelBuilder.Entity("ConfigurationKeyBinding", b => 132 | { 133 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 134 | .WithMany() 135 | .HasForeignKey("ConfigurationsId") 136 | .OnDelete(DeleteBehavior.Cascade) 137 | .IsRequired(); 138 | 139 | b.HasOne("ScreenTemperature.Entities.KeyBinding", null) 140 | .WithMany() 141 | .HasForeignKey("KeyBindingsId") 142 | .OnDelete(DeleteBehavior.Cascade) 143 | .IsRequired(); 144 | }); 145 | 146 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.ColorConfiguration", b => 147 | { 148 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 149 | .WithOne() 150 | .HasForeignKey("ScreenTemperature.Entities.Configurations.ColorConfiguration", "Id") 151 | .OnDelete(DeleteBehavior.Cascade) 152 | .IsRequired(); 153 | }); 154 | 155 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", b => 156 | { 157 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 158 | .WithOne() 159 | .HasForeignKey("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", "Id") 160 | .OnDelete(DeleteBehavior.Cascade) 161 | .IsRequired(); 162 | }); 163 | #pragma warning restore 612, 618 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /Server/HotKeyManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Web.WebView2.Core; 4 | using ScreenTemperature.Entities.Configurations; 5 | using ScreenTemperature.Services; 6 | using System; 7 | using System.Collections.Concurrent; 8 | using System.Diagnostics; 9 | using System.Runtime.InteropServices; 10 | using System.Security.Cryptography; 11 | using System.Threading.Tasks; 12 | using static ScreenTemperature.Win32; 13 | 14 | namespace ScreenTemperature 15 | { 16 | public class HotKeyPressedEventArgs : EventArgs 17 | { 18 | public int KeyCode { get; set; } 19 | public bool Alt { get; set; } 20 | public bool Ctrl { get; set; } 21 | public bool Shift { get; set; } 22 | } 23 | 24 | public class HotKeyManager 25 | { 26 | private static HotKeyManager _instance; 27 | public static HotKeyManager Instance 28 | { 29 | get 30 | { 31 | if(_instance == null) _instance = new HotKeyManager(); 32 | return _instance; 33 | } 34 | } 35 | 36 | public Task Task { get; private set; } 37 | 38 | private BlockingCollection<(uint virtualKeyCode, bool alt, bool control, bool shift, TaskCompletionSource taskCompletionSource)> _hotkeysToRegister = new BlockingCollection<(uint virtualKeyCode, bool alt, bool control, bool shift, TaskCompletionSource taskCompletionSource)>(); 39 | private BlockingCollection<(uint virtualKeyCode, bool alt, bool control, bool shift, TaskCompletionSource taskCompletionSource)> _hotkeysToUnregister = new BlockingCollection<(uint virtualKeyCode, bool alt, bool control, bool shift, TaskCompletionSource taskCompletionSource)>(); 40 | 41 | public delegate void HotKeyPressedEventHandler(object sender, HotKeyPressedEventArgs e); 42 | public static event HotKeyPressedEventHandler HotKeyPressed; 43 | 44 | 45 | HotKeyManager() 46 | { 47 | // private constructor 48 | } 49 | 50 | public void Start(CancellationToken cancellationToken) 51 | { 52 | Task = Task.Run(() => 53 | { 54 | while (!cancellationToken.IsCancellationRequested) 55 | { 56 | try 57 | { 58 | // if an unregistration is in queue 59 | if (_hotkeysToUnregister.Any()) 60 | { 61 | var keyCodeToUnregister = _hotkeysToUnregister.Take(); 62 | 63 | UnregisterHotKey(IntPtr.Zero, GenerateHotKeyId(keyCodeToUnregister.virtualKeyCode, keyCodeToUnregister.alt, keyCodeToUnregister.control, keyCodeToUnregister.shift)); 64 | 65 | // send result 66 | keyCodeToUnregister.taskCompletionSource.SetResult(true); 67 | } 68 | 69 | // if a registration is in queue 70 | if (_hotkeysToRegister.Any()) 71 | { 72 | var keyCodeToRegister = _hotkeysToRegister.Take(); 73 | 74 | var mask = keyCodeToRegister.alt ? (uint)KeyModifiers.Alt : 0; 75 | mask = mask | (keyCodeToRegister.control ? (uint)KeyModifiers.Control : 0); 76 | mask = mask | (keyCodeToRegister.shift ? (uint)KeyModifiers.Shift : 0); 77 | 78 | var hotkeyRegistered = RegisterHotKey(IntPtr.Zero, GenerateHotKeyId(keyCodeToRegister.virtualKeyCode, keyCodeToRegister.alt, keyCodeToRegister.control, keyCodeToRegister.shift), mask, keyCodeToRegister.virtualKeyCode); 79 | 80 | // send result 81 | keyCodeToRegister.taskCompletionSource.SetResult(hotkeyRegistered); 82 | } 83 | 84 | // 0x0080 = QS_HOTKEY -> A WM_HOTKEY message is in the queue. 85 | var availableMessages = GetQueueStatus(0x0080);// Get hotkey messages 86 | 87 | if (availableMessages >> 16 == 0x0080) 88 | { 89 | PeekMessage(out var msg, IntPtr.Zero, WM_HOTKEY, WM_HOTKEY, 1); 90 | 91 | var keyCode = ExtractKeyCodeFromHotKeyId(msg.wParam); 92 | var altWasPressed = ((KeyModifiers)msg.lParam & KeyModifiers.Alt) == KeyModifiers.Alt; 93 | var controlWasPressed = ((KeyModifiers)msg.lParam & KeyModifiers.Control) == KeyModifiers.Control; 94 | var shiftWasPressed = ((KeyModifiers)msg.lParam & KeyModifiers.Shift) == KeyModifiers.Shift; 95 | 96 | HotKeyPressed?.Invoke(this, new HotKeyPressedEventArgs() 97 | { 98 | KeyCode = keyCode, 99 | Alt = altWasPressed, 100 | Ctrl = controlWasPressed, 101 | Shift = shiftWasPressed, 102 | }); 103 | } 104 | 105 | } 106 | catch (Exception ex) { } 107 | } 108 | }); 109 | } 110 | 111 | // generate a unique Id for a key 112 | private int GenerateHotKeyId(uint keycode, bool alt, bool control, bool shift) 113 | { 114 | var mask = (int)keycode << 3; 115 | if (alt) mask = mask | (int)KeyModifiers.Alt; 116 | if (control) mask = mask | (int)KeyModifiers.Control; 117 | if (shift) mask = mask | (int)KeyModifiers.Shift; 118 | 119 | return mask; 120 | } 121 | 122 | private int ExtractKeyCodeFromHotKeyId(nuint id) 123 | { 124 | return (int)id.ToUInt32() >> 3; 125 | } 126 | 127 | public async Task UnregisterHotKeyAsync(int virtualKeyCode, bool alt, bool control, bool shift) 128 | { 129 | var taskCompletionSource = new TaskCompletionSource(); 130 | 131 | _hotkeysToUnregister.Add(((uint)virtualKeyCode, alt, control, shift, taskCompletionSource)); 132 | 133 | return await taskCompletionSource.Task; 134 | } 135 | 136 | public async Task RegisterHotKeyAsync(int virtualKeyCode, bool alt, bool control, bool shift) 137 | { 138 | var taskCompletionSource = new TaskCompletionSource(); 139 | 140 | _hotkeysToRegister.Add(((uint)virtualKeyCode, alt, control, shift, taskCompletionSource)); 141 | 142 | return await taskCompletionSource.Task; 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /Server/Migrations/20250118151747_InitialCreate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | #nullable disable 5 | 6 | namespace ScreenTemperature.Migrations 7 | { 8 | /// 9 | public partial class InitialCreate : Migration 10 | { 11 | /// 12 | protected override void Up(MigrationBuilder migrationBuilder) 13 | { 14 | migrationBuilder.CreateTable( 15 | name: "Configurations", 16 | columns: table => new 17 | { 18 | Id = table.Column(type: "TEXT", nullable: false), 19 | Name = table.Column(type: "TEXT", nullable: false), 20 | DevicePath = table.Column(type: "TEXT", nullable: false), 21 | ApplyBrightness = table.Column(type: "INTEGER", nullable: false), 22 | Brightness = table.Column(type: "INTEGER", nullable: false) 23 | }, 24 | constraints: table => 25 | { 26 | table.PrimaryKey("PK_Configurations", x => x.Id); 27 | }); 28 | 29 | migrationBuilder.CreateTable( 30 | name: "KeyBindings", 31 | columns: table => new 32 | { 33 | Id = table.Column(type: "TEXT", nullable: false), 34 | KeyCode = table.Column(type: "INTEGER", nullable: false), 35 | Alt = table.Column(type: "INTEGER", nullable: false), 36 | Shift = table.Column(type: "INTEGER", nullable: false), 37 | Control = table.Column(type: "INTEGER", nullable: false) 38 | }, 39 | constraints: table => 40 | { 41 | table.PrimaryKey("PK_KeyBindings", x => x.Id); 42 | }); 43 | 44 | migrationBuilder.CreateTable( 45 | name: "ColorConfigurations", 46 | columns: table => new 47 | { 48 | Id = table.Column(type: "TEXT", nullable: false), 49 | ApplyColor = table.Column(type: "INTEGER", nullable: false), 50 | Color = table.Column(type: "TEXT", nullable: false) 51 | }, 52 | constraints: table => 53 | { 54 | table.PrimaryKey("PK_ColorConfigurations", x => x.Id); 55 | table.ForeignKey( 56 | name: "FK_ColorConfigurations_Configurations_Id", 57 | column: x => x.Id, 58 | principalTable: "Configurations", 59 | principalColumn: "Id", 60 | onDelete: ReferentialAction.Cascade); 61 | }); 62 | 63 | migrationBuilder.CreateTable( 64 | name: "TemperatureConfigurations", 65 | columns: table => new 66 | { 67 | Id = table.Column(type: "TEXT", nullable: false), 68 | ApplyIntensity = table.Column(type: "INTEGER", nullable: false), 69 | Intensity = table.Column(type: "INTEGER", nullable: false) 70 | }, 71 | constraints: table => 72 | { 73 | table.PrimaryKey("PK_TemperatureConfigurations", x => x.Id); 74 | table.ForeignKey( 75 | name: "FK_TemperatureConfigurations_Configurations_Id", 76 | column: x => x.Id, 77 | principalTable: "Configurations", 78 | principalColumn: "Id", 79 | onDelete: ReferentialAction.Cascade); 80 | }); 81 | 82 | migrationBuilder.CreateTable( 83 | name: "Commands", 84 | columns: table => new 85 | { 86 | Id = table.Column(type: "TEXT", nullable: false), 87 | KeyBindingId = table.Column(type: "TEXT", nullable: false) 88 | }, 89 | constraints: table => 90 | { 91 | table.PrimaryKey("PK_Commands", x => x.Id); 92 | table.ForeignKey( 93 | name: "FK_Commands_KeyBindings_KeyBindingId", 94 | column: x => x.KeyBindingId, 95 | principalTable: "KeyBindings", 96 | principalColumn: "Id", 97 | onDelete: ReferentialAction.Cascade); 98 | }); 99 | 100 | migrationBuilder.CreateTable( 101 | name: "ApplyConfigurationCommands", 102 | columns: table => new 103 | { 104 | Id = table.Column(type: "TEXT", nullable: false), 105 | ConfigurationId = table.Column(type: "TEXT", nullable: false) 106 | }, 107 | constraints: table => 108 | { 109 | table.PrimaryKey("PK_ApplyConfigurationCommands", x => x.Id); 110 | table.ForeignKey( 111 | name: "FK_ApplyConfigurationCommands_Commands_Id", 112 | column: x => x.Id, 113 | principalTable: "Commands", 114 | principalColumn: "Id", 115 | onDelete: ReferentialAction.Cascade); 116 | table.ForeignKey( 117 | name: "FK_ApplyConfigurationCommands_Configurations_ConfigurationId", 118 | column: x => x.ConfigurationId, 119 | principalTable: "Configurations", 120 | principalColumn: "Id", 121 | onDelete: ReferentialAction.Cascade); 122 | }); 123 | 124 | migrationBuilder.CreateIndex( 125 | name: "IX_ApplyConfigurationCommands_ConfigurationId", 126 | table: "ApplyConfigurationCommands", 127 | column: "ConfigurationId"); 128 | 129 | migrationBuilder.CreateIndex( 130 | name: "IX_Commands_KeyBindingId", 131 | table: "Commands", 132 | column: "KeyBindingId"); 133 | } 134 | 135 | /// 136 | protected override void Down(MigrationBuilder migrationBuilder) 137 | { 138 | migrationBuilder.DropTable( 139 | name: "ApplyConfigurationCommands"); 140 | 141 | migrationBuilder.DropTable( 142 | name: "ColorConfigurations"); 143 | 144 | migrationBuilder.DropTable( 145 | name: "TemperatureConfigurations"); 146 | 147 | migrationBuilder.DropTable( 148 | name: "Commands"); 149 | 150 | migrationBuilder.DropTable( 151 | name: "Configurations"); 152 | 153 | migrationBuilder.DropTable( 154 | name: "KeyBindings"); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Server/Migrations/20250118151747_InitialCreate.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | using ScreenTemperature; 8 | 9 | #nullable disable 10 | 11 | namespace ScreenTemperature.Migrations 12 | { 13 | [DbContext(typeof(DatabaseContext))] 14 | [Migration("20250118151747_InitialCreate")] 15 | partial class InitialCreate 16 | { 17 | /// 18 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 19 | { 20 | #pragma warning disable 612, 618 21 | modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); 22 | 23 | modelBuilder.Entity("ScreenTemperature.Entities.Commands.Command", b => 24 | { 25 | b.Property("Id") 26 | .HasColumnType("TEXT"); 27 | 28 | b.Property("KeyBindingId") 29 | .HasColumnType("TEXT"); 30 | 31 | b.HasKey("Id"); 32 | 33 | b.HasIndex("KeyBindingId"); 34 | 35 | b.ToTable("Commands"); 36 | 37 | b.UseTptMappingStrategy(); 38 | }); 39 | 40 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.Configuration", b => 41 | { 42 | b.Property("Id") 43 | .HasColumnType("TEXT"); 44 | 45 | b.Property("ApplyBrightness") 46 | .HasColumnType("INTEGER"); 47 | 48 | b.Property("Brightness") 49 | .HasColumnType("INTEGER"); 50 | 51 | b.Property("DevicePath") 52 | .IsRequired() 53 | .HasColumnType("TEXT"); 54 | 55 | b.Property("Name") 56 | .IsRequired() 57 | .HasColumnType("TEXT"); 58 | 59 | b.HasKey("Id"); 60 | 61 | b.ToTable("Configurations"); 62 | 63 | b.UseTptMappingStrategy(); 64 | }); 65 | 66 | modelBuilder.Entity("ScreenTemperature.Entities.KeyBinding", b => 67 | { 68 | b.Property("Id") 69 | .HasColumnType("TEXT"); 70 | 71 | b.Property("Alt") 72 | .HasColumnType("INTEGER"); 73 | 74 | b.Property("Control") 75 | .HasColumnType("INTEGER"); 76 | 77 | b.Property("KeyCode") 78 | .HasColumnType("INTEGER"); 79 | 80 | b.Property("Shift") 81 | .HasColumnType("INTEGER"); 82 | 83 | b.HasKey("Id"); 84 | 85 | b.ToTable("KeyBindings"); 86 | }); 87 | 88 | modelBuilder.Entity("ScreenTemperature.Entities.Commands.ApplyConfigurationCommand", b => 89 | { 90 | b.HasBaseType("ScreenTemperature.Entities.Commands.Command"); 91 | 92 | b.Property("ConfigurationId") 93 | .HasColumnType("TEXT"); 94 | 95 | b.HasIndex("ConfigurationId"); 96 | 97 | b.ToTable("ApplyConfigurationCommands"); 98 | }); 99 | 100 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.ColorConfiguration", b => 101 | { 102 | b.HasBaseType("ScreenTemperature.Entities.Configurations.Configuration"); 103 | 104 | b.Property("ApplyColor") 105 | .HasColumnType("INTEGER"); 106 | 107 | b.Property("Color") 108 | .IsRequired() 109 | .HasColumnType("TEXT"); 110 | 111 | b.ToTable("ColorConfigurations"); 112 | }); 113 | 114 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", b => 115 | { 116 | b.HasBaseType("ScreenTemperature.Entities.Configurations.Configuration"); 117 | 118 | b.Property("ApplyIntensity") 119 | .HasColumnType("INTEGER"); 120 | 121 | b.Property("Intensity") 122 | .HasColumnType("INTEGER"); 123 | 124 | b.ToTable("TemperatureConfigurations"); 125 | }); 126 | 127 | modelBuilder.Entity("ScreenTemperature.Entities.Commands.Command", b => 128 | { 129 | b.HasOne("ScreenTemperature.Entities.KeyBinding", "KeyBinding") 130 | .WithMany("Commands") 131 | .HasForeignKey("KeyBindingId") 132 | .OnDelete(DeleteBehavior.Cascade) 133 | .IsRequired(); 134 | 135 | b.Navigation("KeyBinding"); 136 | }); 137 | 138 | modelBuilder.Entity("ScreenTemperature.Entities.Commands.ApplyConfigurationCommand", b => 139 | { 140 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", "Configuration") 141 | .WithMany() 142 | .HasForeignKey("ConfigurationId") 143 | .OnDelete(DeleteBehavior.Cascade) 144 | .IsRequired(); 145 | 146 | b.HasOne("ScreenTemperature.Entities.Commands.Command", null) 147 | .WithOne() 148 | .HasForeignKey("ScreenTemperature.Entities.Commands.ApplyConfigurationCommand", "Id") 149 | .OnDelete(DeleteBehavior.Cascade) 150 | .IsRequired(); 151 | 152 | b.Navigation("Configuration"); 153 | }); 154 | 155 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.ColorConfiguration", b => 156 | { 157 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 158 | .WithOne() 159 | .HasForeignKey("ScreenTemperature.Entities.Configurations.ColorConfiguration", "Id") 160 | .OnDelete(DeleteBehavior.Cascade) 161 | .IsRequired(); 162 | }); 163 | 164 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", b => 165 | { 166 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 167 | .WithOne() 168 | .HasForeignKey("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", "Id") 169 | .OnDelete(DeleteBehavior.Cascade) 170 | .IsRequired(); 171 | }); 172 | 173 | modelBuilder.Entity("ScreenTemperature.Entities.KeyBinding", b => 174 | { 175 | b.Navigation("Commands"); 176 | }); 177 | #pragma warning restore 612, 618 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /Server/Migrations/20250126194909_add-name-on-key-binding.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | using ScreenTemperature; 8 | 9 | #nullable disable 10 | 11 | namespace ScreenTemperature.Migrations 12 | { 13 | [DbContext(typeof(DatabaseContext))] 14 | [Migration("20250126194909_add-name-on-key-binding")] 15 | partial class addnameonkeybinding 16 | { 17 | /// 18 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 19 | { 20 | #pragma warning disable 612, 618 21 | modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); 22 | 23 | modelBuilder.Entity("ScreenTemperature.Entities.Commands.Command", b => 24 | { 25 | b.Property("Id") 26 | .HasColumnType("TEXT"); 27 | 28 | b.Property("KeyBindingId") 29 | .HasColumnType("TEXT"); 30 | 31 | b.HasKey("Id"); 32 | 33 | b.HasIndex("KeyBindingId"); 34 | 35 | b.ToTable("Commands"); 36 | 37 | b.UseTptMappingStrategy(); 38 | }); 39 | 40 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.Configuration", b => 41 | { 42 | b.Property("Id") 43 | .HasColumnType("TEXT"); 44 | 45 | b.Property("ApplyBrightness") 46 | .HasColumnType("INTEGER"); 47 | 48 | b.Property("Brightness") 49 | .HasColumnType("INTEGER"); 50 | 51 | b.Property("DevicePath") 52 | .IsRequired() 53 | .HasColumnType("TEXT"); 54 | 55 | b.Property("Name") 56 | .IsRequired() 57 | .HasColumnType("TEXT"); 58 | 59 | b.HasKey("Id"); 60 | 61 | b.ToTable("Configurations"); 62 | 63 | b.UseTptMappingStrategy(); 64 | }); 65 | 66 | modelBuilder.Entity("ScreenTemperature.Entities.KeyBinding", b => 67 | { 68 | b.Property("Id") 69 | .HasColumnType("TEXT"); 70 | 71 | b.Property("Alt") 72 | .HasColumnType("INTEGER"); 73 | 74 | b.Property("Control") 75 | .HasColumnType("INTEGER"); 76 | 77 | b.Property("KeyCode") 78 | .HasColumnType("INTEGER"); 79 | 80 | b.Property("Name") 81 | .IsRequired() 82 | .HasColumnType("TEXT"); 83 | 84 | b.Property("Shift") 85 | .HasColumnType("INTEGER"); 86 | 87 | b.HasKey("Id"); 88 | 89 | b.ToTable("KeyBindings"); 90 | }); 91 | 92 | modelBuilder.Entity("ScreenTemperature.Entities.Commands.ApplyConfigurationCommand", b => 93 | { 94 | b.HasBaseType("ScreenTemperature.Entities.Commands.Command"); 95 | 96 | b.Property("ConfigurationId") 97 | .HasColumnType("TEXT"); 98 | 99 | b.HasIndex("ConfigurationId"); 100 | 101 | b.ToTable("ApplyConfigurationCommands"); 102 | }); 103 | 104 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.ColorConfiguration", b => 105 | { 106 | b.HasBaseType("ScreenTemperature.Entities.Configurations.Configuration"); 107 | 108 | b.Property("ApplyColor") 109 | .HasColumnType("INTEGER"); 110 | 111 | b.Property("Color") 112 | .IsRequired() 113 | .HasColumnType("TEXT"); 114 | 115 | b.ToTable("ColorConfigurations"); 116 | }); 117 | 118 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", b => 119 | { 120 | b.HasBaseType("ScreenTemperature.Entities.Configurations.Configuration"); 121 | 122 | b.Property("ApplyIntensity") 123 | .HasColumnType("INTEGER"); 124 | 125 | b.Property("Intensity") 126 | .HasColumnType("INTEGER"); 127 | 128 | b.ToTable("TemperatureConfigurations"); 129 | }); 130 | 131 | modelBuilder.Entity("ScreenTemperature.Entities.Commands.Command", b => 132 | { 133 | b.HasOne("ScreenTemperature.Entities.KeyBinding", "KeyBinding") 134 | .WithMany("Commands") 135 | .HasForeignKey("KeyBindingId") 136 | .OnDelete(DeleteBehavior.Cascade) 137 | .IsRequired(); 138 | 139 | b.Navigation("KeyBinding"); 140 | }); 141 | 142 | modelBuilder.Entity("ScreenTemperature.Entities.Commands.ApplyConfigurationCommand", b => 143 | { 144 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", "Configuration") 145 | .WithMany() 146 | .HasForeignKey("ConfigurationId") 147 | .OnDelete(DeleteBehavior.Cascade) 148 | .IsRequired(); 149 | 150 | b.HasOne("ScreenTemperature.Entities.Commands.Command", null) 151 | .WithOne() 152 | .HasForeignKey("ScreenTemperature.Entities.Commands.ApplyConfigurationCommand", "Id") 153 | .OnDelete(DeleteBehavior.Cascade) 154 | .IsRequired(); 155 | 156 | b.Navigation("Configuration"); 157 | }); 158 | 159 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.ColorConfiguration", b => 160 | { 161 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 162 | .WithOne() 163 | .HasForeignKey("ScreenTemperature.Entities.Configurations.ColorConfiguration", "Id") 164 | .OnDelete(DeleteBehavior.Cascade) 165 | .IsRequired(); 166 | }); 167 | 168 | modelBuilder.Entity("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", b => 169 | { 170 | b.HasOne("ScreenTemperature.Entities.Configurations.Configuration", null) 171 | .WithOne() 172 | .HasForeignKey("ScreenTemperature.Entities.Configurations.TemperatureConfiguration", "Id") 173 | .OnDelete(DeleteBehavior.Cascade) 174 | .IsRequired(); 175 | }); 176 | 177 | modelBuilder.Entity("ScreenTemperature.Entities.KeyBinding", b => 178 | { 179 | b.Navigation("Commands"); 180 | }); 181 | #pragma warning restore 612, 618 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /Server/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | /database.db 400 | /database.db-shm 401 | /database.db-wal 402 | -------------------------------------------------------------------------------- /Server/Services/ScreenService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using ScreenTemperature.Entities; 3 | using System.Drawing; 4 | using System.Runtime.InteropServices; 5 | using WindowsDisplayAPI; 6 | using static ScreenTemperature.Win32; 7 | 8 | namespace ScreenTemperature.Services; 9 | 10 | public interface IScreenService 11 | { 12 | Task> GetScreensAsync(); 13 | Task ApplyKelvinToScreenAsync(int value, string devicePath); 14 | Task ApplyColorToScreenAsync(string stringColor, string devicePath); 15 | Task ApplyBrightnessToScreenAsync(int brightness, string devicePath); 16 | } 17 | 18 | public class ScreenService(ILogger logger) : IScreenService 19 | { 20 | private bool GetScreenPhysicalMonitor(string devicePath, out PHYSICAL_MONITOR[] physicalMonitors) 21 | { 22 | physicalMonitors = []; 23 | 24 | var display = WindowsDisplayAPI.Display.GetDisplays()?.FirstOrDefault(x => x.DevicePath == devicePath); 25 | 26 | if (display == null) return false; 27 | 28 | var monitorsHandle = new List(); 29 | 30 | if (!EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, 31 | delegate (IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData) 32 | { 33 | monitorsHandle.Add(hMonitor); 34 | return true; 35 | }, IntPtr.Zero)) return false; 36 | 37 | var displayMonitorHandle = IntPtr.Zero; 38 | 39 | foreach (var monitorHandle in monitorsHandle) 40 | { 41 | var monitorInfo = new MonitorInfo 42 | { 43 | Size = (uint)Marshal.SizeOf(typeof(MonitorInfo)) 44 | }; 45 | 46 | if (!GetMonitorInfo(monitorHandle, ref monitorInfo)) continue; 47 | 48 | if (monitorInfo.DisplayName == display.DisplayName)// if this monitor is the one of the display we are looking for 49 | { 50 | displayMonitorHandle = monitorHandle; 51 | break; 52 | } 53 | } 54 | 55 | if (displayMonitorHandle == IntPtr.Zero) return false; 56 | 57 | if (!GetNumberOfPhysicalMonitorsFromHMONITOR(displayMonitorHandle, out uint numberOfPhysicalMonitors)) return false; 58 | 59 | if (numberOfPhysicalMonitors == 0 || numberOfPhysicalMonitors > 1) return false; 60 | 61 | physicalMonitors = new PHYSICAL_MONITOR[numberOfPhysicalMonitors]; 62 | 63 | if (!GetPhysicalMonitorsFromHMONITOR(displayMonitorHandle, numberOfPhysicalMonitors, physicalMonitors)) return false; 64 | 65 | return true; 66 | } 67 | 68 | private IList _screens; 69 | private Task _screensLoadTask; 70 | 71 | /// 72 | /// Returns a list of all attached screens on this machine 73 | /// 74 | public async Task> GetScreensAsync() 75 | { 76 | if (_screensLoadTask != null) 77 | { 78 | await _screensLoadTask; 79 | 80 | foreach (var screen in _screens)// for each screen, refresh its physical monitor handle (they can change for example when the screen goes to sleep mode) 81 | { 82 | if (!screen.IsDDCSupported) continue; 83 | 84 | DestroyPhysicalMonitors(1, screen.PhysicalMonitors);// delete previous handles 85 | var physicalMonitor = GetScreenPhysicalMonitor(screen.DevicePath, out var physicalMonitors);// get new ones 86 | 87 | screen.PhysicalMonitors = physicalMonitors; 88 | } 89 | 90 | return _screens; 91 | } 92 | 93 | _screensLoadTask = new Task(() => { }); 94 | _screens = new List(); 95 | 96 | foreach (var display in Display.GetDisplays())// for each windows display 97 | { 98 | if(!GetScreenPhysicalMonitor(display.DevicePath, out PHYSICAL_MONITOR[] physicalMonitors)) continue; 99 | 100 | uint min = 0, max = 0, current = 0; 101 | bool isDDCsupported = false; 102 | bool isBrightnessSupported = false; 103 | 104 | isDDCsupported = GetMonitorCapabilities(physicalMonitors[0].hPhysicalMonitor, out MC_CAPS pdwMonitorCapabilities, out MC_SUPPORTED_COLOR_TEMPERATURE pdwSupportedColorTemperatures); 105 | 106 | if (isDDCsupported) 107 | { 108 | isBrightnessSupported = pdwMonitorCapabilities.HasFlag(MC_CAPS.MC_CAPS_BRIGHTNESS); 109 | } 110 | 111 | if (isBrightnessSupported) 112 | GetMonitorBrightness(physicalMonitors[0].hPhysicalMonitor, ref min, ref current, ref max); 113 | 114 | _screens.Add(new Screen() 115 | { 116 | Label = display.ToPathDisplayTarget().FriendlyName, 117 | Width = display.CurrentSetting.Resolution.Width, 118 | Height = display.CurrentSetting.Resolution.Height, 119 | IsPrimary = display.IsGDIPrimary, 120 | X = display.CurrentSetting.Position.X, 121 | Y = display.CurrentSetting.Position.Y, 122 | DevicePath = display.DevicePath, 123 | IsDDCSupported = isDDCsupported, 124 | IsBrightnessSupported = isBrightnessSupported, 125 | MinBrightness = min, 126 | MaxBrightness = max, 127 | CurrentBrightness = current, 128 | PhysicalMonitors = physicalMonitors, 129 | }); 130 | } 131 | 132 | _screensLoadTask.Start(); 133 | 134 | return _screens; 135 | } 136 | 137 | private bool ApplyRGBToScreen(float red, float green, float blue, string devicePath) 138 | { 139 | var array = new ushort[3 * 256]; 140 | 141 | for (var ik = 0; ik < 256; ik++) 142 | { 143 | array[ik] = (ushort)(ik * red); 144 | array[256 + ik] = (ushort)(ik * green); 145 | array[512 + ik] = (ushort)(ik * blue); 146 | } 147 | 148 | var display = WindowsDisplayAPI.Display.GetDisplays().FirstOrDefault(x => x.DevicePath == devicePath); 149 | 150 | if (display == null) throw new Exception($"Could not find screen '{devicePath}'."); 151 | 152 | var hdc = CreateDC(display.DisplayName, display.DevicePath, null, IntPtr.Zero); 153 | 154 | var pinnedArray = GCHandle.Alloc(array, GCHandleType.Pinned); 155 | IntPtr pointer = pinnedArray.AddrOfPinnedObject(); 156 | 157 | var succeeded = SetDeviceGammaRamp(hdc.ToInt32(), pointer); 158 | 159 | DeleteDC(hdc); 160 | 161 | pinnedArray.Free(); 162 | 163 | return succeeded; 164 | } 165 | 166 | /// 167 | /// Changes screen color from kelvin value 168 | /// Thanks to Tanner Helland for his algorithm http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/ 169 | /// 170 | public async Task ApplyKelvinToScreenAsync(int value, string devicePath) 171 | { 172 | if(value < 1000 || value > 40000) 173 | { 174 | throw new Exception("Invalid value."); 175 | } 176 | 177 | float kelvin = value; 178 | var temperature = kelvin / 100; 179 | 180 | float red, green, blue; 181 | 182 | if (temperature <= 66) 183 | { 184 | red = 255; 185 | } 186 | else 187 | { 188 | red = temperature - 60; 189 | red = 329.698727446f * ((float)Math.Pow(red, -0.1332047592)); 190 | 191 | if (red < 0) red = 0; 192 | if (red > 255) red = 255; 193 | } 194 | 195 | if (temperature <= 66) 196 | { 197 | green = temperature; 198 | green = 99.4708025861f * (float)Math.Log(green) - 161.1195681661f; 199 | 200 | if (green < 0) green = 0; 201 | if (green > 255) green = 255; 202 | } 203 | else 204 | { 205 | green = temperature - 60; 206 | green = 288.1221695283f * ((float)Math.Pow(green, -0.0755148492)); 207 | 208 | if (green < 0) green = 0; 209 | if (green > 255) green = 255; 210 | } 211 | 212 | 213 | if (temperature >= 66) 214 | { 215 | blue = 255; 216 | } 217 | else 218 | { 219 | if (temperature <= 19) 220 | { 221 | blue = 0; 222 | } 223 | else 224 | { 225 | blue = temperature - 10; 226 | blue = 138.5177312231f * (float)Math.Log(blue) - 305.0447927307f; 227 | if (blue < 0) blue = 0; 228 | if (blue > 255) blue = 255; 229 | } 230 | } 231 | 232 | if (value == 6600) 233 | { 234 | red = 255; 235 | green = 255; 236 | blue = 255; 237 | } 238 | 239 | return ApplyRGBToScreen(red, green, blue, devicePath); 240 | } 241 | 242 | public async Task ApplyColorToScreenAsync(string stringColor, string devicePath) 243 | { 244 | ColorConverter converter = new ColorConverter(); 245 | var color = (Color?)converter.ConvertFromString(stringColor); 246 | 247 | if (!color.HasValue) 248 | { 249 | throw new Exception("Invalid value."); 250 | } 251 | 252 | return ApplyRGBToScreen(color.Value.R, color.Value.G, color.Value.B, devicePath); 253 | } 254 | 255 | public async Task ApplyBrightnessToScreenAsync(int brightness, string devicePath) 256 | { 257 | var screens = await GetScreensAsync(); 258 | var screen = screens.FirstOrDefault(x => x.DevicePath == devicePath); 259 | 260 | if (screen == null) throw new Exception($"Could not find screen '{devicePath}'."); 261 | 262 | var succeeded = false; 263 | 264 | // calculate maximum when minimum is 0 265 | var maximum = screen.MaxBrightness - screen.MinBrightness; 266 | 267 | // brightness is in percentage 268 | var valueToApply = (uint)brightness * maximum / 100 + screen.MinBrightness; 269 | 270 | succeeded = SetMonitorBrightness(screen.PhysicalMonitors[0].hPhysicalMonitor, valueToApply); 271 | 272 | return succeeded; 273 | } 274 | } -------------------------------------------------------------------------------- /Server/WebApp.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Hosting; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.Win32; 7 | using ScreenTemperature.Entities.Configurations; 8 | using ScreenTemperature.Hubs; 9 | using ScreenTemperature.Middlewares; 10 | using ScreenTemperature.Services; 11 | using System.Diagnostics; 12 | using Vernou.Swashbuckle.HttpResultsAdapter; 13 | 14 | namespace ScreenTemperature 15 | { 16 | public class WebApp 17 | { 18 | private static WebApp _instance; 19 | public static WebApp Instance 20 | { 21 | get 22 | { 23 | if (_instance == null) 24 | { 25 | _instance = new WebApp(); 26 | } 27 | 28 | return _instance; 29 | } 30 | } 31 | 32 | WebApp() 33 | { 34 | // private constructor 35 | } 36 | 37 | public WebApplication WebApplication { get; private set; } 38 | public Task Task { get; private set; } 39 | 40 | public void Start(CancellationToken cancellationToken) 41 | { 42 | var builder = WebApplication.CreateBuilder(); 43 | 44 | #region Services 45 | 46 | // Add services to the container. 47 | builder.Services.AddControllers(); 48 | 49 | builder.Services.AddSingleton(); 50 | builder.Services.AddScoped(); 51 | 52 | builder.Services.AddDbContext(); 53 | 54 | if (builder.Environment.IsDevelopment()) 55 | { 56 | builder.Services.AddSwaggerGen(c => 57 | { 58 | c.OperationFilter(); 59 | c.EnableAnnotations(enableAnnotationsForInheritance: true, enableAnnotationsForPolymorphism: true); 60 | c.UseOneOfForPolymorphism(); 61 | }); 62 | } 63 | 64 | builder.Services.AddSignalR(); 65 | 66 | if (builder.Environment.IsDevelopment()) 67 | { 68 | builder.Services.AddCors(options => 69 | { 70 | options.AddPolicy(name: "devCORS", 71 | policy => 72 | { 73 | policy.WithOrigins("http://localhost:5173").AllowAnyMethod().AllowAnyHeader().AllowCredentials(); 74 | //policy.WithMethods() 75 | }); 76 | }); 77 | } 78 | 79 | #endregion 80 | 81 | 82 | WebApplication = builder.Build(); 83 | 84 | using (var scope = WebApplication.Services.CreateScope()) 85 | { 86 | try 87 | { 88 | var databaseContext = scope.ServiceProvider.GetRequiredService(); 89 | databaseContext.Database.Migrate(); 90 | } 91 | catch (Exception ex) 92 | { 93 | var logger = scope.ServiceProvider.GetRequiredService>(); 94 | logger.LogError(ex, "An error occurred creating the DB."); 95 | } 96 | } 97 | 98 | Task.Run(async () => 99 | { 100 | using (var scope = WebApplication.Services.CreateScope()) 101 | { 102 | var databaseContext = scope.ServiceProvider.GetRequiredService(); 103 | 104 | #region Update parameters 105 | 106 | var parametersService = scope.ServiceProvider.GetRequiredService(); 107 | var parameters = await parametersService.GetParametersAsync(); 108 | await parametersService.UpdateParametersAsync(parameters); 109 | 110 | #endregion 111 | 112 | #region Load screens in memory 113 | 114 | var screenService = scope.ServiceProvider.GetRequiredService(); 115 | await screenService.GetScreensAsync();// load screens in memory 116 | 117 | #endregion 118 | 119 | #region Register HotKeys 120 | 121 | var keyBindings = await databaseContext.KeyBindings.ToListAsync(); 122 | 123 | foreach (var keyBinding in keyBindings) 124 | { 125 | _ = HotKeyManager.Instance.RegisterHotKeyAsync(keyBinding.KeyCode, keyBinding.Alt, keyBinding.Control, false); 126 | } 127 | 128 | HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; 129 | 130 | #endregion 131 | 132 | #region Apply configurations 133 | 134 | var configurations = await databaseContext.Configurations.ToListAsync(); 135 | 136 | foreach (var config in configurations) 137 | { 138 | if (config.ApplyAtStartup) 139 | { 140 | if (config.ApplyBrightness) 141 | await screenService.ApplyBrightnessToScreenAsync(config.Brightness, config.DevicePath); 142 | 143 | if (config is TemperatureConfiguration temperatureConfiguration && temperatureConfiguration.ApplyIntensity) 144 | await screenService.ApplyKelvinToScreenAsync(temperatureConfiguration.Intensity, temperatureConfiguration.DevicePath); 145 | 146 | if (config is ColorConfiguration colorConfiguration && colorConfiguration.ApplyColor) 147 | await screenService.ApplyColorToScreenAsync(colorConfiguration.Color, colorConfiguration.DevicePath); 148 | } 149 | } 150 | 151 | #endregion 152 | } 153 | }); 154 | 155 | WebApplication.UseMiddleware(); 156 | 157 | // Configure the HTTP request pipeline. 158 | if (!WebApplication.Environment.IsDevelopment()) 159 | { 160 | //app.UseExceptionHandler("/Home/Error"); 161 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 162 | WebApplication.UseHsts(); 163 | } 164 | 165 | WebApplication.UseDefaultFiles(); 166 | WebApplication.UseStaticFiles(); 167 | 168 | if (builder.Environment.IsDevelopment()) 169 | { 170 | WebApplication.UseCors("devCORS"); 171 | } 172 | 173 | //WebApplication.UseAuthorization(); 174 | 175 | if (builder.Environment.IsDevelopment()) 176 | { 177 | WebApplication.UseSwagger(); 178 | WebApplication.UseSwaggerUI(); 179 | } 180 | 181 | WebApplication.MapControllers(); 182 | 183 | WebApplication.MapHub("/hub"); 184 | 185 | WebApplication.Urls.Add("http://localhost:61983"); 186 | 187 | Task = WebApplication.RunAsync(cancellationToken); 188 | } 189 | 190 | private async void HotKeyManager_HotKeyPressed(object sender, HotKeyPressedEventArgs e) 191 | { 192 | using (var scope = WebApplication.Services.CreateScope()) 193 | { 194 | var databaseContext = scope.ServiceProvider.GetRequiredService(); 195 | var screenService = scope.ServiceProvider.GetRequiredService(); 196 | 197 | var matchingBinding = databaseContext.KeyBindings.Include(x => x.Configurations).SingleOrDefault(x => x.KeyCode == e.KeyCode && x.Alt == e.Alt && x.Control == e.Ctrl); 198 | 199 | if (matchingBinding != null) 200 | { 201 | foreach (var config in matchingBinding.Configurations) 202 | { 203 | var result = false; 204 | 205 | if (config.ApplyBrightness) 206 | { 207 | try 208 | { 209 | result = await screenService.ApplyBrightnessToScreenAsync(config.Brightness, config.DevicePath); 210 | } 211 | catch (Exception ex) { } 212 | 213 | //await Clients.All.SendAsync("ApplyTemperatureResult", result); 214 | 215 | } 216 | 217 | if (config is ColorConfiguration colorConfiguration) 218 | { 219 | if (colorConfiguration.ApplyColor) 220 | { 221 | result = false; 222 | 223 | try 224 | { 225 | result = await screenService.ApplyColorToScreenAsync(colorConfiguration.Color, config.DevicePath); 226 | } 227 | catch (Exception ex) { } 228 | } 229 | } 230 | else if (config is TemperatureConfiguration temperatureConfiguration) 231 | { 232 | if (temperatureConfiguration.ApplyIntensity) 233 | { 234 | result = false; 235 | 236 | try 237 | { 238 | result = await screenService.ApplyKelvinToScreenAsync(temperatureConfiguration.Intensity, config.DevicePath); 239 | } 240 | catch (Exception ex) { } 241 | } 242 | } 243 | else 244 | { 245 | throw new NotImplementedException(); 246 | } 247 | } 248 | } 249 | } 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /Server/Win32.cs: -------------------------------------------------------------------------------- 1 | 2 | using ScreenTemperature.Entities; 3 | using System; 4 | using System.Drawing; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using WindowsDisplayAPI.Native.Structures; 8 | 9 | namespace ScreenTemperature; 10 | 11 | internal static class Win32 12 | { 13 | public const uint WS_OVERLAPPED = 0x00000000; 14 | public const uint WS_MAXIMIZEBOX = 0x00010000; 15 | public const uint WS_MINIMIZEBOX = 0x00020000; 16 | public const uint WS_THICKFRAME = 0x00040000; 17 | public const uint WS_SYSMENU = 0x00080000; 18 | public const uint WS_CAPTION = 0x00C00000; 19 | public const uint WS_VISIBLE = 0x10000000; 20 | public const uint WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX; 21 | 22 | public delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); 23 | 24 | [StructLayout(LayoutKind.Sequential)] 25 | public struct WNDCLASSEX 26 | { 27 | public uint cbSize; 28 | public uint style; 29 | [MarshalAs(UnmanagedType.FunctionPtr)] 30 | public WndProc lpfnWndProc; 31 | public int cbClsExtra; 32 | public int cbWndExtra; 33 | public IntPtr hInstance; 34 | public IntPtr hIcon; 35 | public IntPtr hCursor; 36 | public IntPtr hbrBackground; 37 | public string lpszMenuName; 38 | public string lpszClassName; 39 | public IntPtr hIconSm; 40 | } 41 | 42 | [DllImport("user32.dll")] 43 | public static extern void PostQuitMessage(int nExitCode); 44 | 45 | [DllImport("user32.dll")] 46 | public static extern IntPtr DefWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam); 47 | 48 | [DllImport("user32.dll", SetLastError = true)] 49 | public static extern IntPtr CreateWindowEx( 50 | uint dwExStyle, 51 | string lpClassName, 52 | string lpWindowName, 53 | uint dwStyle, 54 | int x, 55 | int y, 56 | int nWidth, 57 | int nHeight, 58 | IntPtr hWndParent, 59 | IntPtr hMenu, 60 | IntPtr hInstance, 61 | IntPtr lpParam 62 | ); 63 | 64 | [DllImport("user32.dll", SetLastError = true)] 65 | public static extern short RegisterClassEx(ref WNDCLASSEX lpwcx); 66 | 67 | [DllImport("gdi32.dll")] 68 | public static extern bool SetDeviceGammaRamp(int hdc, IntPtr ramp); 69 | 70 | [DllImport("gdi32.dll")] 71 | public static extern IntPtr CreateDC(string lpszDriver, string? lpszDevice, string? lpszOutput, IntPtr lpInitData); 72 | 73 | [DllImport("gdi32.dll")] 74 | public static extern bool DeleteDC(IntPtr hdc); 75 | 76 | [DllImport("Dxva2.dll")] 77 | public static extern bool GetMonitorBrightness(IntPtr hdc, ref uint pdwMinimumBrightness, ref uint pdwCurrentBrightness, ref uint pdwMaximumBrightness); 78 | 79 | [DllImport("Dxva2.dll")] 80 | public static extern bool SetMonitorBrightness(IntPtr hMonitor, uint dwNewBrightness); 81 | 82 | [DllImport("Dxva2.dll")] 83 | public static extern bool GetMonitorCapabilities(IntPtr hdc, out MC_CAPS pdwMonitorCapabilities, out MC_SUPPORTED_COLOR_TEMPERATURE pdwSupportedColorTemperatures); 84 | 85 | [StructLayout(LayoutKind.Sequential)] 86 | public struct Rect 87 | { 88 | public int left; 89 | public int top; 90 | public int right; 91 | public int bottom; 92 | } 93 | 94 | [DllImport("user32.dll")] 95 | public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, EnumMonitorsDelegate lpfnEnum, IntPtr dwData); 96 | 97 | public delegate bool EnumMonitorsDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData); 98 | 99 | [Flags] 100 | internal enum MonitorInfoFlags : uint 101 | { 102 | None = 0, 103 | Primary = 1 104 | } 105 | 106 | [StructLayout(LayoutKind.Sequential)] 107 | public struct MonitorInfo 108 | { 109 | internal uint Size; 110 | public readonly Rect Bounds; 111 | public readonly Rect WorkingArea; 112 | public readonly MonitorInfoFlags Flags; 113 | 114 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] 115 | public readonly string DisplayName; 116 | } 117 | 118 | [DllImport("user32")] 119 | public static extern bool GetMonitorInfo(IntPtr monitorHandle, ref MonitorInfo monitorInfo); 120 | 121 | [DllImport("Dxva2.dll")] 122 | public static extern bool GetNumberOfPhysicalMonitorsFromHMONITOR(IntPtr hMonitor, out uint pdwNumberOfPhysicalMonitors); 123 | 124 | [DllImport("Dxva2.dll")] 125 | public static extern bool GetPhysicalMonitorsFromHMONITOR(IntPtr hMonitor, uint dwPhysicalMonitorArraySize, [Out] PHYSICAL_MONITOR[] pPhysicalMonitorArray); 126 | 127 | [DllImport("Dxva2.dll")] 128 | public static extern bool DestroyPhysicalMonitors(uint dwPhysicalMonitorArraySize, [In] PHYSICAL_MONITOR[] pPhysicalMonitorArray); 129 | 130 | // found on https://github.com/emoacht/Monitorian/blob/master/Source/Monitorian.Core/Models/Monitor/MonitorConfiguration.cs 131 | [Flags] 132 | public enum MC_CAPS 133 | { 134 | MC_CAPS_NONE = 0x00000000, 135 | MC_CAPS_MONITOR_TECHNOLOGY_TYPE = 0x00000001, 136 | MC_CAPS_BRIGHTNESS = 0x00000002, 137 | MC_CAPS_CONTRAST = 0x00000004, 138 | MC_CAPS_COLOR_TEMPERATURE = 0x00000008, 139 | MC_CAPS_RED_GREEN_BLUE_GAIN = 0x00000010, 140 | MC_CAPS_RED_GREEN_BLUE_DRIVE = 0x00000020, 141 | MC_CAPS_DEGAUSS = 0x00000040, 142 | MC_CAPS_DISPLAY_AREA_POSITION = 0x00000080, 143 | MC_CAPS_DISPLAY_AREA_SIZE = 0x00000100, 144 | MC_CAPS_RESTORE_FACTORY_DEFAULTS = 0x00000400, 145 | MC_CAPS_RESTORE_FACTORY_COLOR_DEFAULTS = 0x00000800, 146 | MC_RESTORE_FACTORY_DEFAULTS_ENABLES_MONITOR_SETTINGS = 0x00001000 147 | } 148 | 149 | // found on https://github.com/emoacht/Monitorian/blob/master/Source/Monitorian.Core/Models/Monitor/MonitorConfiguration.cs 150 | [Flags] 151 | public enum MC_SUPPORTED_COLOR_TEMPERATURE 152 | { 153 | MC_SUPPORTED_COLOR_TEMPERATURE_NONE = 0x00000000, 154 | MC_SUPPORTED_COLOR_TEMPERATURE_4000K = 0x00000001, 155 | MC_SUPPORTED_COLOR_TEMPERATURE_5000K = 0x00000002, 156 | MC_SUPPORTED_COLOR_TEMPERATURE_6500K = 0x00000004, 157 | MC_SUPPORTED_COLOR_TEMPERATURE_7500K = 0x00000008, 158 | MC_SUPPORTED_COLOR_TEMPERATURE_8200K = 0x00000010, 159 | MC_SUPPORTED_COLOR_TEMPERATURE_9300K = 0x00000020, 160 | MC_SUPPORTED_COLOR_TEMPERATURE_10000K = 0x00000040, 161 | MC_SUPPORTED_COLOR_TEMPERATURE_11500K = 0x00000080 162 | } 163 | 164 | public struct POINT 165 | { 166 | public int x; 167 | public int y; 168 | } 169 | 170 | [StructLayout(LayoutKind.Sequential, Pack = 8)] 171 | public struct MSG 172 | { 173 | public IntPtr hwnd; 174 | public uint message; 175 | public UIntPtr wParam; 176 | public UIntPtr lParam; 177 | public uint time; 178 | public POINT pt; 179 | } 180 | 181 | [Flags] 182 | public enum KeyModifiers : uint 183 | { 184 | Alt = 1, 185 | Control = 2, 186 | Shift = 4 187 | } 188 | 189 | [DllImport("user32.dll")] 190 | public static extern uint GetQueueStatus(uint flags); 191 | 192 | [DllImport("user32.dll")] 193 | public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg); 194 | 195 | [DllImport("user32.dll", SetLastError = true)] 196 | public static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); 197 | 198 | [DllImport("user32.dll")] 199 | public static extern bool UnregisterHotKey(IntPtr hWnd, int id); 200 | 201 | [DllImport("Kernel32.dll")] 202 | public static extern uint GetLastError(); 203 | 204 | [DllImport("user32.dll")] 205 | public static extern sbyte GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, 206 | uint wMsgFilterMax); 207 | 208 | [DllImport("user32.dll")] 209 | public static extern IntPtr DispatchMessage(ref MSG lpmsg); 210 | 211 | [DllImport("user32.dll")] 212 | public static extern bool TranslateMessage(ref MSG lpMsg); 213 | 214 | [StructLayout(LayoutKind.Sequential)] 215 | public struct NOTIFYICONDATA 216 | { 217 | public int cbSize; 218 | public IntPtr hwnd; 219 | public int uID; 220 | public int uFlags; 221 | public int uCallbackMessage; 222 | public IntPtr hIcon; 223 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] 224 | public string szTip; 225 | public int dwState; 226 | public int dwStateMask; 227 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] 228 | public string szInfo; 229 | public int uTimeoutOrVersion; 230 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] 231 | public string szInfoTitle; 232 | public int dwInfoFlags; 233 | public Guid guidItem; 234 | } 235 | 236 | public const int NIF_MESSAGE = 0x00000001; 237 | public const int NIF_ICON = 0x00000002; 238 | public const int NIF_TIP = 0x00000004; 239 | public const int NIF_SHOWTIP = 0x00000080; 240 | public const int NIF_INFO = 0x00000010; 241 | public const int NOTIFYICON_VERSION_4 = 4; 242 | public const int NIM_SETVERSION = 0x00000004; 243 | 244 | public const int WM_USER = 0x0400; 245 | public const int WM_TRAYICON = WM_USER + 1; 246 | public const int IDI_APPLICATION = 32512; 247 | 248 | public const int WM_LBUTTONDOWN = 0x0201; 249 | public const int WM_RBUTTONDOWN = 0x0204; 250 | public const int WM_DESTROY = 0x0002; 251 | public const int WM_HOTKEY = 0x0312; 252 | public const int WM_SIZE = 0x0005; 253 | public const int WM_QUIT = 0x0012; 254 | 255 | [DllImport("Shell32.dll")] 256 | public static extern bool Shell_NotifyIcon(uint dwMessage, ref NOTIFYICONDATA lpData); 257 | 258 | [DllImport("Comctl32.dll")] 259 | public static extern long LoadIconMetric(IntPtr hinst, string pszName, int lims, ref IntPtr phico); 260 | 261 | [DllImport("user32.dll")] 262 | public static extern IntPtr LoadIcon(IntPtr hInstance, IntPtr lpIconName); 263 | 264 | public const uint IMAGE_ICON = 1; 265 | public const uint LR_LOADFROMFILE = 0x00000010; 266 | 267 | [DllImport("user32.dll", CharSet = CharSet.Auto)] 268 | public static extern IntPtr LoadImage(IntPtr hInstance, string lpszName, uint uType, int cxDesired, int cyDesired, uint fuLoad); 269 | 270 | public const int SW_HIDE = 0; 271 | public const int SW_SHOWNORMAL = 1; 272 | public const int SW_SHOWMINIMIZED = 2; 273 | public const int SW_SHOWMAXIMIZED = 3; 274 | public const int SW_SHOWNOACTIVATE = 4; 275 | public const int SW_SHOW = 5; 276 | public const int SW_MINIMIZE = 6; 277 | public const int SW_SHOWMINNOACTIVE = 7; 278 | public const int SW_SHOWNA = 8; 279 | public const int SW_RESTORE = 9; 280 | public const int SW_SHOWDEFAULT = 10; 281 | public const int SW_FORCEMINIMIZE = 11; 282 | 283 | [DllImport("user32.dll")] 284 | public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); 285 | 286 | [DllImport("user32.dll")] 287 | public static extern bool UpdateWindow(IntPtr hWnd); 288 | 289 | [StructLayout(LayoutKind.Sequential)] 290 | public struct RECT 291 | { 292 | public int left, top, right, bottom; 293 | } 294 | 295 | [DllImport("user32.dll")] 296 | public static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect); 297 | 298 | [DllImport("kernel32.dll")] 299 | public static extern IntPtr GetConsoleWindow(); 300 | 301 | [DllImport("kernel32.dll")] 302 | [return: MarshalAs(UnmanagedType.Bool)] 303 | public static extern bool AllocConsole(); 304 | 305 | [DllImport("kernel32.dll", CharSet = CharSet.Auto)] 306 | public static extern uint GetModuleFileName(IntPtr hModule, StringBuilder lpFilename, uint nSize); 307 | } -------------------------------------------------------------------------------- /Client/src/components/Pages/CreateOrUpdateKeyBindingPage.vue: -------------------------------------------------------------------------------- 1 | 222 | 223 | 372 | 373 | --------------------------------------------------------------------------------