mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-02 08:28:55 +02:00
Compare commits
10 Commits
dev/migrie
...
copilot/up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c412e85cfc | ||
|
|
049cbccd21 | ||
|
|
4308eb65a9 | ||
|
|
5520ae4cfa | ||
|
|
beddc3b065 | ||
|
|
088da21a70 | ||
|
|
befb5c672e | ||
|
|
578554d157 | ||
|
|
e4f98897ce | ||
|
|
be1e749574 |
6
.github/actions/spell-check/expect.txt
vendored
6
.github/actions/spell-check/expect.txt
vendored
@@ -2343,3 +2343,9 @@ YTimer
|
||||
zamora
|
||||
zonability
|
||||
Zorder
|
||||
LLMHF
|
||||
RIGHTBUTTON
|
||||
SIZEALL
|
||||
grabandmove
|
||||
GRABANDMOVEMODULEINTERFACE
|
||||
INITCOMMONCONTROLSEX
|
||||
|
||||
232
.github/workflows/scheduled-issue-labeling.yml
vendored
Normal file
232
.github/workflows/scheduled-issue-labeling.yml
vendored
Normal file
@@ -0,0 +1,232 @@
|
||||
name: Scheduled Issue Product Labeling
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "20 */6 * * *" # Every 6 hours at :20
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
permissions:
|
||||
models: read
|
||||
issues: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
label-issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Label issues missing Product labels
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
// ── Product label mapping ──────────────────────────────────
|
||||
// Canonical list of Product-* labels used in this repo,
|
||||
// derived from .github/skills/release-note-generation/references/step2-labeling.md
|
||||
const PRODUCT_LABELS = [
|
||||
"Product-Advanced Paste",
|
||||
"Product-Always on Top",
|
||||
"Product-Awake",
|
||||
"Product-ColorPicker",
|
||||
"Product-Command not found",
|
||||
"Product-Command Palette",
|
||||
"Product-CropAndLock",
|
||||
"Product-Cursor Wrap",
|
||||
"Product-Environment Variables",
|
||||
"Product-FancyZones",
|
||||
"Product-File Explorer",
|
||||
"Product-File Locksmith",
|
||||
"Product-Find My Mouse",
|
||||
"Product-Hosts",
|
||||
"Product-Image Resizer",
|
||||
"Product-Keyboard Manager",
|
||||
"Product-LightSwitch",
|
||||
"Product-Mouse Highlighter",
|
||||
"Product-Mouse Jump",
|
||||
"Product-Mouse Pointer Crosshairs",
|
||||
"Product-Mouse Without Borders",
|
||||
"Product-New+",
|
||||
"Product-Peek",
|
||||
"Product-PowerRename",
|
||||
"Product-PowerToys Run",
|
||||
"Product-Quick Accent",
|
||||
"Product-Registry Preview",
|
||||
"Product-Screen Ruler",
|
||||
"Product-Settings",
|
||||
"Product-Shortcut Guide",
|
||||
"Product-Text Extractor",
|
||||
"Product-Workspaces",
|
||||
"Product-ZoomIt",
|
||||
];
|
||||
|
||||
// Map from bug-report "Area(s) with issue?" dropdown values
|
||||
// to Product-* labels (used as strong hints when the issue body
|
||||
// contains the area dropdown answer).
|
||||
const AREA_TO_LABEL = {
|
||||
"Advanced Paste": "Product-Advanced Paste",
|
||||
"Always on Top": "Product-Always on Top",
|
||||
"Awake": "Product-Awake",
|
||||
"ColorPicker": "Product-ColorPicker",
|
||||
"Command not found": "Product-Command not found",
|
||||
"Command Palette": "Product-Command Palette",
|
||||
"Crop and Lock": "Product-CropAndLock",
|
||||
"Environment Variables": "Product-Environment Variables",
|
||||
"FancyZones": "Product-FancyZones",
|
||||
"FancyZones Editor": "Product-FancyZones",
|
||||
"File Locksmith": "Product-File Locksmith",
|
||||
"File Explorer: Preview Pane": "Product-File Explorer",
|
||||
"File Explorer: Thumbnail preview": "Product-File Explorer",
|
||||
"Hosts File Editor": "Product-Hosts",
|
||||
"Image Resizer": "Product-Image Resizer",
|
||||
"Keyboard Manager": "Product-Keyboard Manager",
|
||||
"Light Switch": "Product-LightSwitch",
|
||||
"Mouse Utilities": "Product-Find My Mouse",
|
||||
"Mouse Without Borders": "Product-Mouse Without Borders",
|
||||
"New+": "Product-New+",
|
||||
"Peek": "Product-Peek",
|
||||
"PowerRename": "Product-PowerRename",
|
||||
"PowerToys Run": "Product-PowerToys Run",
|
||||
"Quick Accent": "Product-Quick Accent",
|
||||
"Registry Preview": "Product-Registry Preview",
|
||||
"Screen ruler": "Product-Screen Ruler",
|
||||
"Shortcut Guide": "Product-Shortcut Guide",
|
||||
"TextExtractor": "Product-Text Extractor",
|
||||
"Workspaces": "Product-Workspaces",
|
||||
"ZoomIt": "Product-ZoomIt",
|
||||
};
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────
|
||||
function hasProductLabel(labels) {
|
||||
return labels.some((l) => l.name.startsWith("Product-"));
|
||||
}
|
||||
|
||||
// Try to extract the area from the structured bug-report body
|
||||
// (the "Area(s) with issue?" dropdown).
|
||||
function extractAreaFromBody(body) {
|
||||
if (!body) return null;
|
||||
// The rendered issue body contains a heading followed by the selected values
|
||||
const areaMatch = body.match(
|
||||
/### Area\(s\) with issue\?\s*\n+(.+?)(?:\n###|\n\n|$)/s
|
||||
);
|
||||
if (!areaMatch) return null;
|
||||
const areaText = areaMatch[1].trim();
|
||||
if (areaText === "_No response_" || areaText === "General") return null;
|
||||
// Could be comma-separated; take the first specific one
|
||||
const areas = areaText.split(",").map((a) => a.trim());
|
||||
for (const area of areas) {
|
||||
if (AREA_TO_LABEL[area]) return AREA_TO_LABEL[area];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use GitHub Models to classify an issue when the dropdown area
|
||||
// is not available or is "General".
|
||||
const MAX_BODY_LENGTH = 3000; // Truncate body to stay within model token limits while keeping enough context
|
||||
const MAX_COMPLETION_TOKENS = 60; // Enough for a Product-* label name with some margin
|
||||
async function classifyWithAI(title, body) {
|
||||
const truncatedBody = (body || "").slice(0, MAX_BODY_LENGTH);
|
||||
const labelList = PRODUCT_LABELS.join("\n- ");
|
||||
|
||||
const prompt = `You are a GitHub issue triager for the microsoft/PowerToys repository.
|
||||
|
||||
Given the issue title and body below, determine which ONE Product label best fits.
|
||||
Reply with ONLY the label name (e.g. "Product-FancyZones") or "UNKNOWN" if you cannot determine it.
|
||||
|
||||
Available labels:
|
||||
- ${labelList}
|
||||
|
||||
Issue title: ${title}
|
||||
|
||||
Issue body:
|
||||
${truncatedBody}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
"https://models.github.ai/inference/chat/completions",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "openai/gpt-4o",
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
max_tokens: MAX_COMPLETION_TOKENS,
|
||||
temperature: 0,
|
||||
}),
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
core.warning(`AI classification failed: ${response.status} ${response.statusText}`);
|
||||
return null;
|
||||
}
|
||||
const data = await response.json();
|
||||
const answer = data.choices?.[0]?.message?.content?.trim();
|
||||
if (!answer || answer === "UNKNOWN") return null;
|
||||
// Validate the answer is a known label
|
||||
if (PRODUCT_LABELS.includes(answer)) return answer;
|
||||
// Try fuzzy match (the model may include extra text)
|
||||
const found = PRODUCT_LABELS.find((l) => answer.includes(l));
|
||||
return found || null;
|
||||
} catch (err) {
|
||||
core.warning(`AI classification error: ${err.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Main ───────────────────────────────────────────────────
|
||||
const MAX_ISSUES = 50; // Process up to 50 issues per run
|
||||
let labeled = 0;
|
||||
let skipped = 0;
|
||||
|
||||
core.info("Searching for open issues with Needs-Triage but no Product-* label...");
|
||||
|
||||
// Paginate through open issues labeled Needs-Triage
|
||||
for await (const response of github.paginate.iterator(
|
||||
github.rest.issues.listForRepo,
|
||||
{
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: "open",
|
||||
labels: "Needs-Triage",
|
||||
sort: "created",
|
||||
direction: "desc",
|
||||
per_page: 100,
|
||||
}
|
||||
)) {
|
||||
for (const issue of response.data) {
|
||||
if (labeled + skipped >= MAX_ISSUES) break;
|
||||
// Skip pull requests (the API returns them too)
|
||||
if (issue.pull_request) continue;
|
||||
if (hasProductLabel(issue.labels)) continue;
|
||||
|
||||
core.info(`Processing #${issue.number}: ${issue.title}`);
|
||||
|
||||
// 1) Try structured area dropdown first (fast, no AI needed)
|
||||
let label = extractAreaFromBody(issue.body);
|
||||
|
||||
// 2) Fall back to AI classification
|
||||
if (!label) {
|
||||
label = await classifyWithAI(issue.title, issue.body);
|
||||
}
|
||||
|
||||
if (label) {
|
||||
core.info(` → Applying "${label}" to #${issue.number}`);
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: [label],
|
||||
});
|
||||
labeled++;
|
||||
} else {
|
||||
core.info(` → Could not determine product label for #${issue.number}, skipping.`);
|
||||
skipped++;
|
||||
}
|
||||
}
|
||||
if (labeled + skipped >= MAX_ISSUES) break;
|
||||
}
|
||||
|
||||
core.info(`Done. Labeled: ${labeled}, Skipped: ${skipped}`);
|
||||
@@ -250,6 +250,9 @@
|
||||
"PowerToys.ZoomItModuleInterface.dll",
|
||||
"PowerToys.ZoomItSettingsInterop.dll",
|
||||
|
||||
"PowerToys.GrabAndMove.exe",
|
||||
"PowerToys.GrabAndMoveModuleInterface.dll",
|
||||
|
||||
"WinUI3Apps\\PowerToys.Settings.dll",
|
||||
"WinUI3Apps\\PowerToys.Settings.exe",
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ Thank you for using PowerToys!
|
||||
| Microsoft.PowerToys.AwakeIndefinitelyKeepAwakeEvent | Triggered when the system is set to stay awake indefinitely. |
|
||||
| Microsoft.PowerToys.AwakeNoKeepAwakeEvent | Occurs when Awake is turned off, allowing the computer to enter sleep mode. |
|
||||
| Microsoft.PowerToys.AwakeTimedKeepAwakeEvent | Triggered when the system is kept awake for a specified time duration. |
|
||||
| Microsoft.PowerToys.Awake_CLICommand | Triggered when an Awake CLI command is executed, logging the command name and success status. |
|
||||
|
||||
### Color Picker
|
||||
|
||||
@@ -204,6 +205,7 @@ Thank you for using PowerToys!
|
||||
| Microsoft.PowerToys.FileLocksmith_Invoked | Occurs when File Locksmith is invoked. |
|
||||
| Microsoft.PowerToys.FileLocksmith_InvokedRet | Triggered when File Locksmith invocation returns a result. |
|
||||
| Microsoft.PowerToys.FileLocksmith_QueryContextMenuError | Occurs when there is an error querying the context menu for File Locksmith. |
|
||||
| Microsoft.PowerToys.FileLocksmith_CLICommand | Triggered when a File Locksmith CLI command is executed, logging the operation mode (query, kill, query-wait, query-json, or help) and success status. |
|
||||
|
||||
### FileExplorerAddOns
|
||||
|
||||
@@ -258,6 +260,7 @@ Thank you for using PowerToys!
|
||||
| Microsoft.PowerToys.ImageResizer_Invoked | Occurs when Image Resizer is invoked by the user. |
|
||||
| Microsoft.PowerToys.ImageResizer_InvokedRet | Fires when the Image Resizer operation is completed and returns a result. |
|
||||
| Microsoft.PowerToys.ImageResizer_QueryContextMenuError | Triggered when there is an error querying the context menu for Image Resizer. |
|
||||
| Microsoft.PowerToys.ImageResizer_CLICommand | Triggered when an Image Resizer CLI command is executed, logging the command name and success status. |
|
||||
|
||||
### Keyboard Manager
|
||||
|
||||
|
||||
@@ -1036,6 +1036,10 @@
|
||||
<Project Path="src/modules/ZoomIt/ZoomItModuleInterface/ZoomItModuleInterface.vcxproj" Id="e4585179-2ac1-4d5f-a3ff-cfc5392f694c" />
|
||||
<Project Path="src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.vcxproj" Id="ca7d8106-30b9-4aec-9d05-b69b31b8c461" />
|
||||
</Folder>
|
||||
<Folder Name="/modules/GrabAndMove/">
|
||||
<Project Path="src/modules/GrabAndMove/GrabAndMove/GrabAndMove.vcxproj" Id="568c4c30-2e3c-4c2c-a691-007362073765" />
|
||||
<Project Path="src/modules/GrabAndMove/GrabAndMoveModuleInterface/GrabAndMoveModuleInterface.vcxproj" Id="2c3f7770-4e57-46b7-8dc1-7428a383d0db" />
|
||||
</Folder>
|
||||
<Folder Name="/settings-ui/">
|
||||
<Project Path="src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
@@ -1100,6 +1104,8 @@
|
||||
<BuildDependency Project="src/modules/launcher/Microsoft.Launcher/Microsoft.Launcher.vcxproj" />
|
||||
<BuildDependency Project="src/modules/LightSwitch/LightSwitchModuleInterface/LightSwitchModuleInterface.vcxproj" />
|
||||
<BuildDependency Project="src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj" />
|
||||
<BuildDependency Project="src/modules/GrabAndMove/GrabAndMoveModuleInterface/GrabAndMoveModuleInterface.vcxproj" />
|
||||
<BuildDependency Project="src/modules/GrabAndMove/GrabAndMove/GrabAndMove.vcxproj" />
|
||||
<BuildDependency Project="src/modules/powerrename/dll/PowerRenameExt.vcxproj" />
|
||||
<BuildDependency Project="src/modules/powerrename/lib/PowerRenameLib.vcxproj" />
|
||||
<BuildDependency Project="src/modules/previewpane/Common/PreviewHandlerCommon.csproj" />
|
||||
|
||||
@@ -36,6 +36,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredPowerDisplayEnabledValue());
|
||||
}
|
||||
GpoRuleConfigured GPOWrapper::GetConfiguredGrabAndMoveEnabledValue()
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredGrabAndMoveEnabledValue());
|
||||
}
|
||||
GpoRuleConfigured GPOWrapper::GetConfiguredFancyZonesEnabledValue()
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredFancyZonesEnabledValue());
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredPowerDisplayEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredGrabAndMoveEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue();
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace PowerToys
|
||||
static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredPowerDisplayEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredGrabAndMoveEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue();
|
||||
|
||||
@@ -71,5 +71,10 @@ namespace PowerToys.GPOWrapperProjection
|
||||
{
|
||||
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredLightSwitchEnabledValue();
|
||||
}
|
||||
|
||||
public static GpoRuleConfigured GetConfiguredGrabAndMoveEnabledValue()
|
||||
{
|
||||
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredGrabAndMoveEnabledValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace ManagedCommon
|
||||
ShortcutGuide,
|
||||
PowerOCR,
|
||||
Workspaces,
|
||||
GrabAndMove,
|
||||
ZoomIt,
|
||||
GeneralSettings,
|
||||
}
|
||||
|
||||
@@ -145,6 +145,10 @@ namespace CommonSharedConstants
|
||||
|
||||
// Path to the events used by ZoomIt
|
||||
const wchar_t ZOOMIT_REFRESH_SETTINGS_EVENT[] = L"Local\\PowerToysZoomIt-RefreshSettingsEvent-f053a563-d519-4b0d-8152-a54489c13324";
|
||||
|
||||
// Path to the events used by GrabAndMove
|
||||
const wchar_t GRABANDMOVE_REFRESH_SETTINGS_EVENT[] = L"Local\\PowerToysGrabAndMove-RefreshSettingsEvent-a7b3c1d2-4e5f-6a7b-8c9d-0e1f2a3b4c5d";
|
||||
const wchar_t GRABANDMOVE_EXIT_EVENT[] = L"Local\\PowerToysGrabAndMove-ExitEvent-b8c4d2e3-5f6a-7b8c-9d0e-1f2a3b4c5d6e";
|
||||
const wchar_t ZOOMIT_EXIT_EVENT[] = L"Local\\PowerToysZoomIt-ExitEvent-36641ce6-df02-4eac-abea-a3fbf9138220";
|
||||
const wchar_t ZOOMIT_ZOOM_EVENT[] = L"Local\\PowerToysZoomIt-ZoomEvent-1e4190d7-94bc-4ad5-adc0-9a8fd07cb393";
|
||||
const wchar_t ZOOMIT_DRAW_EVENT[] = L"Local\\PowerToysZoomIt-DrawEvent-56338997-404d-4549-bd9a-d132b6766975";
|
||||
|
||||
@@ -84,6 +84,7 @@ struct LogSettings
|
||||
inline const static std::string zoomItLoggerName = "zoom-it";
|
||||
inline const static std::string lightSwitchLoggerName = "light-switch";
|
||||
inline const static std::string powerDisplayLoggerName = "powerdisplay";
|
||||
inline const static std::string grabAndMoveLoggerName = "grabandmove";
|
||||
inline const static int retention = 30;
|
||||
std::wstring logLevel;
|
||||
LogSettings();
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace powertoys_gpo
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_CROP_AND_LOCK = L"ConfigureEnabledUtilityCropAndLock";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_LIGHT_SWITCH = L"ConfigureEnabledUtilityLightSwitch";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_POWER_DISPLAY = L"ConfigureEnabledUtilityPowerDisplay";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_GRAB_AND_MOVE = L"ConfigureEnabledUtilityGrabAndMove";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_FANCYZONES = L"ConfigureEnabledUtilityFancyZones";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_FILE_LOCKSMITH = L"ConfigureEnabledUtilityFileLocksmith";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_SVG_PREVIEW = L"ConfigureEnabledUtilityFileExplorerSVGPreview";
|
||||
@@ -317,6 +318,11 @@ namespace powertoys_gpo
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_POWER_DISPLAY);
|
||||
}
|
||||
|
||||
inline gpo_rule_configured_t getConfiguredGrabAndMoveEnabledValue()
|
||||
{
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_GRAB_AND_MOVE);
|
||||
}
|
||||
|
||||
inline gpo_rule_configured_t getConfiguredFancyZonesEnabledValue()
|
||||
{
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_FANCYZONES);
|
||||
|
||||
@@ -496,23 +496,119 @@ private:
|
||||
|
||||
if (!GetGUIThreadInfo(0, &gui_info))
|
||||
{
|
||||
Logger::warn(L"Auto-copy: GetGUIThreadInfo failed (error={})", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
HWND target = gui_info.hwndFocus ? gui_info.hwndFocus : gui_info.hwndActive;
|
||||
if (!target)
|
||||
{
|
||||
Logger::warn(L"Auto-copy: no focused or active window found");
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD_PTR result = 0;
|
||||
return SendMessageTimeout(target,
|
||||
WM_COPY,
|
||||
0,
|
||||
0,
|
||||
SMTO_ABORTIFHUNG | SMTO_BLOCK,
|
||||
50,
|
||||
&result) != 0;
|
||||
auto sendResult = SendMessageTimeout(target, WM_COPY, 0, 0, SMTO_ABORTIFHUNG | SMTO_BLOCK, 50, &result);
|
||||
return sendResult != 0;
|
||||
}
|
||||
|
||||
// Helper: poll clipboard sequence number for a change from initial_sequence.
|
||||
// Returns true if the sequence number changed within the given number of polls.
|
||||
bool poll_clipboard_sequence(DWORD initial_sequence, int poll_attempts, std::chrono::milliseconds poll_delay)
|
||||
{
|
||||
for (int poll = 0; poll < poll_attempts; ++poll)
|
||||
{
|
||||
if (GetClipboardSequenceNumber() != initial_sequence)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
std::this_thread::sleep_for(poll_delay);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper: send Ctrl+C via SendInput, releasing any held modifier keys first
|
||||
// (the hotkey combination may still have modifiers physically pressed).
|
||||
bool send_ctrl_c_input()
|
||||
{
|
||||
std::vector<INPUT> inputs;
|
||||
|
||||
// Release all modifier keys that are currently held down from the hotkey.
|
||||
// Without this, the target app sees e.g. Win+Shift+Ctrl+C instead of just Ctrl+C.
|
||||
try_inject_modifier_key_up(inputs, VK_LCONTROL);
|
||||
try_inject_modifier_key_up(inputs, VK_RCONTROL);
|
||||
try_inject_modifier_key_up(inputs, VK_LWIN);
|
||||
try_inject_modifier_key_up(inputs, VK_RWIN);
|
||||
try_inject_modifier_key_up(inputs, VK_LSHIFT);
|
||||
try_inject_modifier_key_up(inputs, VK_RSHIFT);
|
||||
try_inject_modifier_key_up(inputs, VK_LMENU);
|
||||
try_inject_modifier_key_up(inputs, VK_RMENU);
|
||||
|
||||
// Ctrl down
|
||||
{
|
||||
INPUT input_event = {};
|
||||
input_event.type = INPUT_KEYBOARD;
|
||||
input_event.ki.wVk = VK_CONTROL;
|
||||
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
|
||||
inputs.push_back(input_event);
|
||||
}
|
||||
|
||||
// C down
|
||||
{
|
||||
INPUT input_event = {};
|
||||
input_event.type = INPUT_KEYBOARD;
|
||||
input_event.ki.wVk = 0x43; // C
|
||||
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
|
||||
inputs.push_back(input_event);
|
||||
}
|
||||
|
||||
// C up
|
||||
{
|
||||
INPUT input_event = {};
|
||||
input_event.type = INPUT_KEYBOARD;
|
||||
input_event.ki.wVk = 0x43; // C
|
||||
input_event.ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
|
||||
inputs.push_back(input_event);
|
||||
}
|
||||
|
||||
// Ctrl up
|
||||
{
|
||||
INPUT input_event = {};
|
||||
input_event.type = INPUT_KEYBOARD;
|
||||
input_event.ki.wVk = VK_CONTROL;
|
||||
input_event.ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
|
||||
inputs.push_back(input_event);
|
||||
}
|
||||
|
||||
// Restore modifiers that were held down
|
||||
try_inject_modifier_key_restore(inputs, VK_LCONTROL);
|
||||
try_inject_modifier_key_restore(inputs, VK_RCONTROL);
|
||||
try_inject_modifier_key_restore(inputs, VK_LWIN);
|
||||
try_inject_modifier_key_restore(inputs, VK_RWIN);
|
||||
try_inject_modifier_key_restore(inputs, VK_LSHIFT);
|
||||
try_inject_modifier_key_restore(inputs, VK_RSHIFT);
|
||||
try_inject_modifier_key_restore(inputs, VK_LMENU);
|
||||
try_inject_modifier_key_restore(inputs, VK_RMENU);
|
||||
|
||||
// Prevent Start Menu from activating after Win key release/restore
|
||||
INPUT dummyEvent = {};
|
||||
dummyEvent.type = INPUT_KEYBOARD;
|
||||
dummyEvent.ki.wVk = 0xFF;
|
||||
dummyEvent.ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
inputs.push_back(dummyEvent);
|
||||
|
||||
auto uSent = SendInput(static_cast<UINT>(inputs.size()), inputs.data(), sizeof(INPUT));
|
||||
if (uSent != inputs.size())
|
||||
{
|
||||
DWORD errorCode = GetLastError();
|
||||
auto errorMessage = get_last_error_message(errorCode);
|
||||
Logger::error(L"SendInput failed for Ctrl+C. Expected to send {} inputs and sent only {}. {}", inputs.size(), uSent, errorMessage.has_value() ? errorMessage.value() : L"");
|
||||
Trace::AdvancedPaste_Error(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"input.SendInput");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool send_copy_selection()
|
||||
@@ -526,78 +622,30 @@ private:
|
||||
for (int attempt = 0; attempt < copy_attempts; ++attempt)
|
||||
{
|
||||
const auto initial_sequence = GetClipboardSequenceNumber();
|
||||
copy_succeeded = try_send_copy_message();
|
||||
|
||||
if (!copy_succeeded)
|
||||
// Strategy 1: Try WM_COPY message (works for standard Win32 controls)
|
||||
bool wm_copy_sent = try_send_copy_message();
|
||||
|
||||
if (wm_copy_sent)
|
||||
{
|
||||
std::vector<INPUT> inputs;
|
||||
|
||||
// send Ctrl+C (key downs and key ups)
|
||||
{
|
||||
INPUT input_event = {};
|
||||
input_event.type = INPUT_KEYBOARD;
|
||||
input_event.ki.wVk = VK_CONTROL;
|
||||
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
|
||||
inputs.push_back(input_event);
|
||||
}
|
||||
|
||||
{
|
||||
INPUT input_event = {};
|
||||
input_event.type = INPUT_KEYBOARD;
|
||||
input_event.ki.wVk = 0x43; // C
|
||||
// Avoid triggering detection by the centralized keyboard hook.
|
||||
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
|
||||
inputs.push_back(input_event);
|
||||
}
|
||||
|
||||
{
|
||||
INPUT input_event = {};
|
||||
input_event.type = INPUT_KEYBOARD;
|
||||
input_event.ki.wVk = 0x43; // C
|
||||
input_event.ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
// Avoid triggering detection by the centralized keyboard hook.
|
||||
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
|
||||
inputs.push_back(input_event);
|
||||
}
|
||||
|
||||
{
|
||||
INPUT input_event = {};
|
||||
input_event.type = INPUT_KEYBOARD;
|
||||
input_event.ki.wVk = VK_CONTROL;
|
||||
input_event.ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
|
||||
inputs.push_back(input_event);
|
||||
}
|
||||
|
||||
auto uSent = SendInput(static_cast<UINT>(inputs.size()), inputs.data(), sizeof(INPUT));
|
||||
if (uSent != inputs.size())
|
||||
{
|
||||
DWORD errorCode = GetLastError();
|
||||
auto errorMessage = get_last_error_message(errorCode);
|
||||
Logger::error(L"SendInput failed for Ctrl+C. Expected to send {} inputs and sent only {}. {}", inputs.size(), uSent, errorMessage.has_value() ? errorMessage.value() : L"");
|
||||
Trace::AdvancedPaste_Error(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"input.SendInput");
|
||||
}
|
||||
else
|
||||
if (poll_clipboard_sequence(initial_sequence, clipboard_poll_attempts, clipboard_poll_delay))
|
||||
{
|
||||
copy_succeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (copy_succeeded)
|
||||
// Strategy 2: If WM_COPY didn't work, try SendInput Ctrl+C (works for Electron, browsers, etc.)
|
||||
if (!copy_succeeded)
|
||||
{
|
||||
bool sequence_changed = false;
|
||||
for (int poll_attempt = 0; poll_attempt < clipboard_poll_attempts; ++poll_attempt)
|
||||
const auto sequence_before_ctrl_c = GetClipboardSequenceNumber();
|
||||
|
||||
if (send_ctrl_c_input())
|
||||
{
|
||||
if (GetClipboardSequenceNumber() != initial_sequence)
|
||||
if (poll_clipboard_sequence(sequence_before_ctrl_c, clipboard_poll_attempts, clipboard_poll_delay))
|
||||
{
|
||||
sequence_changed = true;
|
||||
break;
|
||||
copy_succeeded = true;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(clipboard_poll_delay);
|
||||
}
|
||||
|
||||
copy_succeeded = sequence_changed;
|
||||
}
|
||||
|
||||
if (copy_succeeded)
|
||||
@@ -611,6 +659,11 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
if (!copy_succeeded)
|
||||
{
|
||||
Logger::warn(L"Auto-copy: all {} copy attempts failed — the target application did not update the clipboard after WM_COPY and Ctrl+C", copy_attempts);
|
||||
}
|
||||
|
||||
return copy_succeeded;
|
||||
}
|
||||
|
||||
@@ -977,6 +1030,7 @@ public:
|
||||
{
|
||||
if (!send_copy_selection())
|
||||
{
|
||||
Logger::warn(L"Auto-copy: failed to copy selection for custom action index {} — aborting action", custom_action_index);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IPr
|
||||
if (argc < 2)
|
||||
{
|
||||
Logger::warn("No arguments provided");
|
||||
return { 1, get_usage(strings) };
|
||||
return { 1, get_usage(strings), L"help" };
|
||||
}
|
||||
|
||||
bool json_output = false;
|
||||
@@ -156,18 +156,18 @@ CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IPr
|
||||
catch (...)
|
||||
{
|
||||
Logger::error("Invalid timeout value");
|
||||
return { 1, strings.GetString(IDS_ERROR_INVALID_TIMEOUT) };
|
||||
return { 1, strings.GetString(IDS_ERROR_INVALID_TIMEOUT), L"query-wait" };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error("Timeout argument missing");
|
||||
return { 1, strings.GetString(IDS_ERROR_TIMEOUT_ARG) };
|
||||
return { 1, strings.GetString(IDS_ERROR_TIMEOUT_ARG), L"query-wait" };
|
||||
}
|
||||
}
|
||||
else if (arg == L"--help")
|
||||
{
|
||||
return { 0, get_usage(strings) };
|
||||
return { 0, get_usage(strings), L"help" };
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -178,7 +178,7 @@ CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IPr
|
||||
if (paths.empty())
|
||||
{
|
||||
Logger::error("No paths specified");
|
||||
return { 1, strings.GetString(IDS_ERROR_NO_PATHS) };
|
||||
return { 1, strings.GetString(IDS_ERROR_NO_PATHS), L"query" };
|
||||
}
|
||||
|
||||
Logger::info("Processing {} paths", paths.size());
|
||||
@@ -213,13 +213,13 @@ CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IPr
|
||||
{
|
||||
Logger::warn("Timeout waiting for files to be unlocked");
|
||||
ss << strings.GetString(IDS_TIMEOUT);
|
||||
return { 1, ss.str() };
|
||||
return { 1, ss.str(), L"query-wait" };
|
||||
}
|
||||
}
|
||||
|
||||
Sleep(200);
|
||||
}
|
||||
return { 0, ss.str() };
|
||||
return { 0, ss.str(), L"query-wait" };
|
||||
}
|
||||
|
||||
auto results = finder.find(paths);
|
||||
@@ -244,5 +244,6 @@ CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IPr
|
||||
output_ss << get_text(results, strings);
|
||||
}
|
||||
|
||||
return { 0, output_ss.str() };
|
||||
std::wstring cmd_name = kill ? L"kill" : (json_output ? L"query-json" : L"query");
|
||||
return { 0, output_ss.str(), cmd_name };
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ struct CommandResult
|
||||
{
|
||||
int exit_code;
|
||||
std::wstring output;
|
||||
std::wstring command_name;
|
||||
};
|
||||
|
||||
struct IProcessFinder
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "pch.h"
|
||||
#include "CLILogic.h"
|
||||
#include "FileLocksmithLib/FileLocksmith.h"
|
||||
#include "FileLocksmithLib/Trace.h"
|
||||
#include <iostream>
|
||||
#include "resource.h"
|
||||
#include <common/logger/logger.h>
|
||||
@@ -47,6 +48,7 @@ struct RealStringProvider : IStringProvider
|
||||
int wmain(int argc, wchar_t* argv[])
|
||||
{
|
||||
winrt::init_apartment();
|
||||
Trace::RegisterProvider();
|
||||
LoggerHelpers::init_logger(L"FileLocksmithCLI", L"", LogSettings::fileLocksmithLoggerName);
|
||||
Logger::info("FileLocksmithCLI started");
|
||||
|
||||
@@ -65,7 +67,10 @@ int wmain(int argc, wchar_t* argv[])
|
||||
Logger::info("Command succeeded");
|
||||
}
|
||||
|
||||
Trace::CLICommand(result.command_name.c_str(), result.exit_code == 0);
|
||||
|
||||
std::wcout << result.output;
|
||||
Trace::UnregisterProvider();
|
||||
return result.exit_code;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace FileLocksmithCLIUnitTests
|
||||
auto result = run_command(1, argv, finder, terminator, strings);
|
||||
|
||||
Assert::AreEqual(1, result.exit_code);
|
||||
Assert::AreEqual(std::wstring(L"help"), result.command_name);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestHelp)
|
||||
@@ -64,6 +65,7 @@ namespace FileLocksmithCLIUnitTests
|
||||
auto result = run_command(2, argv, finder, terminator, strings);
|
||||
|
||||
Assert::AreEqual(0, result.exit_code);
|
||||
Assert::AreEqual(std::wstring(L"help"), result.command_name);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestFindProcesses)
|
||||
@@ -77,6 +79,7 @@ namespace FileLocksmithCLIUnitTests
|
||||
auto result = run_command(2, argv, finder, terminator, strings);
|
||||
|
||||
Assert::AreEqual(0, result.exit_code);
|
||||
Assert::AreEqual(std::wstring(L"query"), result.command_name);
|
||||
Assert::IsTrue(result.output.find(L"123") != std::wstring::npos);
|
||||
Assert::IsTrue(result.output.find(L"process") != std::wstring::npos);
|
||||
}
|
||||
@@ -94,6 +97,7 @@ namespace FileLocksmithCLIUnitTests
|
||||
Microsoft::VisualStudio::CppUnitTestFramework::Logger::WriteMessage(result.output.c_str());
|
||||
|
||||
Assert::AreEqual(0, result.exit_code);
|
||||
Assert::AreEqual(std::wstring(L"query-json"), result.command_name);
|
||||
Assert::IsTrue(result.output.find(L"\"pid\"") != std::wstring::npos);
|
||||
Assert::IsTrue(result.output.find(L"123") != std::wstring::npos);
|
||||
}
|
||||
@@ -109,6 +113,7 @@ namespace FileLocksmithCLIUnitTests
|
||||
auto result = run_command(3, argv, finder, terminator, strings);
|
||||
|
||||
Assert::AreEqual(0, result.exit_code);
|
||||
Assert::AreEqual(std::wstring(L"kill"), result.command_name);
|
||||
Assert::AreEqual((size_t)1, terminator.terminatedPids.size());
|
||||
Assert::AreEqual((DWORD)123, terminator.terminatedPids[0]);
|
||||
}
|
||||
@@ -125,6 +130,7 @@ namespace FileLocksmithCLIUnitTests
|
||||
auto result = run_command(5, argv, finder, terminator, strings);
|
||||
|
||||
Assert::AreEqual(1, result.exit_code);
|
||||
Assert::AreEqual(std::wstring(L"query-wait"), result.command_name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -49,3 +49,14 @@ void Trace::QueryContextMenuError(_In_ HRESULT hr) noexcept
|
||||
TraceLoggingHResult(hr),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
|
||||
}
|
||||
|
||||
void Trace::CLICommand(_In_ PCWSTR commandName, _In_ bool successful) noexcept
|
||||
{
|
||||
TraceLoggingWriteWrapper(
|
||||
g_hProvider,
|
||||
"FileLocksmith_CLICommand",
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
|
||||
TraceLoggingWideString(commandName, "CommandName"),
|
||||
TraceLoggingBoolean(successful, "Successful"));
|
||||
}
|
||||
|
||||
@@ -11,4 +11,5 @@ public:
|
||||
static void Invoked() noexcept;
|
||||
static void InvokedRet(_In_ HRESULT hr) noexcept;
|
||||
static void QueryContextMenuError(_In_ HRESULT hr) noexcept;
|
||||
static void CLICommand(_In_ PCWSTR commandName, _In_ bool successful) noexcept;
|
||||
};
|
||||
|
||||
BIN
src/modules/GrabAndMove/GrabAndMove/GrabAndMove.ico
Normal file
BIN
src/modules/GrabAndMove/GrabAndMove/GrabAndMove.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 766 B |
10
src/modules/GrabAndMove/GrabAndMove/GrabAndMove.manifest
Normal file
10
src/modules/GrabAndMove/GrabAndMove/GrabAndMove.manifest
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"
|
||||
xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<asmv3:application>
|
||||
<asmv3:windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
</assembly>
|
||||
102
src/modules/GrabAndMove/GrabAndMove/GrabAndMove.rc
Normal file
102
src/modules/GrabAndMove/GrabAndMove/GrabAndMove.rc
Normal file
@@ -0,0 +1,102 @@
|
||||
// Microsoft Visual C++ generated resource script.
|
||||
//
|
||||
#include "resource.h"
|
||||
#include "../../../common/version/version.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#include "winres.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// English (United States) resources
|
||||
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
#pragma code_page(1252)
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TEXTINCLUDE
|
||||
//
|
||||
|
||||
1 TEXTINCLUDE
|
||||
BEGIN
|
||||
"resource.h\0"
|
||||
END
|
||||
|
||||
2 TEXTINCLUDE
|
||||
BEGIN
|
||||
"#include ""winres.h""\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
3 TEXTINCLUDE
|
||||
BEGIN
|
||||
"\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Icon
|
||||
//
|
||||
|
||||
// Icon with lowest ID value placed first to ensure application icon
|
||||
// remains consistent on all systems.
|
||||
IDI_APP_ICON ICON "GrabAndMove.ico"
|
||||
|
||||
#endif // English (United States) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION FILE_VERSION
|
||||
PRODUCTVERSION PRODUCT_VERSION
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
|
||||
BEGIN
|
||||
VALUE "CompanyName", COMPANY_NAME
|
||||
VALUE "FileDescription", FILE_DESCRIPTION
|
||||
VALUE "FileVersion", FILE_VERSION_STRING
|
||||
VALUE "InternalName", INTERNAL_NAME
|
||||
VALUE "LegalCopyright", COPYRIGHT_NOTE
|
||||
VALUE "OriginalFilename", ORIGINAL_FILENAME
|
||||
VALUE "ProductName", PRODUCT_NAME
|
||||
VALUE "ProductVersion", PRODUCT_VERSION_STRING
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
|
||||
END
|
||||
END
|
||||
|
||||
#ifndef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 3 resource.
|
||||
//
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#endif // not APSTUDIO_INVOKED
|
||||
197
src/modules/GrabAndMove/GrabAndMove/GrabAndMove.vcxproj
Normal file
197
src/modules/GrabAndMove/GrabAndMove/GrabAndMove.vcxproj
Normal file
@@ -0,0 +1,197 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{568c4c30-2e3c-4c2c-a691-007362073765}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>GrabAndMove</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>GrabAndMove</ProjectName>
|
||||
<TargetName>PowerToys.GrabAndMove</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<OutDir>$(RepoRoot)$(Platform)\$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>
|
||||
$(RepoRoot)src\common;
|
||||
$(RepoRoot)src\common\SettingsAPI;
|
||||
$(RepoRoot)src\;
|
||||
%(AdditionalIncludeDirectories)
|
||||
</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>WindowsApp.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EntryPointSymbol>wWinMainCRTStartup</EntryPointSymbol>
|
||||
<AdditionalDependencies>comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EntryPointSymbol>wWinMainCRTStartup</EntryPointSymbol>
|
||||
<AdditionalDependencies>comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EntryPointSymbol>wWinMainCRTStartup</EntryPointSymbol>
|
||||
<AdditionalDependencies>comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EntryPointSymbol>wWinMainCRTStartup</EntryPointSymbol>
|
||||
<AdditionalDependencies>comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="GrabAndMove.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(RepoRoot)src\common\SettingsAPI\SettingsAPI.vcxproj">
|
||||
<Project>{6955446D-23F7-4023-9BB3-8657F904AF99}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="GrabAndMove.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Manifest Include="GrabAndMove.manifest" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
</Project>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="pch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{195243ad-53ca-40c9-8879-b9efef4fc26d}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{9bdf974b-a58f-4af8-aed8-4882381f7172}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{0e19fd51-9939-4511-b8cb-d144c0e2a670}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="GrabAndMove.ico">
|
||||
<Filter>Resource Files</Filter>
|
||||
</Image>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="GrabAndMove.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
1151
src/modules/GrabAndMove/GrabAndMove/main.cpp
Normal file
1151
src/modules/GrabAndMove/GrabAndMove/main.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1
src/modules/GrabAndMove/GrabAndMove/pch.cpp
Normal file
1
src/modules/GrabAndMove/GrabAndMove/pch.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "pch.h"
|
||||
14
src/modules/GrabAndMove/GrabAndMove/pch.h
Normal file
14
src/modules/GrabAndMove/GrabAndMove/pch.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <commctrl.h>
|
||||
#include <TraceLoggingProvider.h>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
21
src/modules/GrabAndMove/GrabAndMove/resource.h
Normal file
21
src/modules/GrabAndMove/GrabAndMove/resource.h
Normal file
@@ -0,0 +1,21 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by GrabAndMove.rc
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#define IDI_APP_ICON 101
|
||||
#define IDR_TRAY_MENU 102
|
||||
#define IDM_EXIT 1001
|
||||
|
||||
#define WM_TRAY_ICON (WM_USER + 1)
|
||||
|
||||
//////////////////////////////
|
||||
// Non-localizable
|
||||
|
||||
#define FILE_DESCRIPTION "PowerToys GrabAndMove"
|
||||
#define INTERNAL_NAME "PowerToys.GrabAndMove"
|
||||
#define ORIGINAL_FILENAME "PowerToys.GrabAndMove.exe"
|
||||
|
||||
// Non-localizable
|
||||
//////////////////////////////
|
||||
@@ -0,0 +1,36 @@
|
||||
#include <windows.h>
|
||||
#include "resource.h"
|
||||
#include "../../../common/version/version.h"
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION FILE_VERSION
|
||||
PRODUCTVERSION PRODUCT_VERSION
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_DLL
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
|
||||
BEGIN
|
||||
VALUE "CompanyName", COMPANY_NAME
|
||||
VALUE "FileDescription", FILE_DESCRIPTION
|
||||
VALUE "FileVersion", FILE_VERSION_STRING
|
||||
VALUE "InternalName", INTERNAL_NAME
|
||||
VALUE "LegalCopyright", COPYRIGHT_NOTE
|
||||
VALUE "OriginalFilename", ORIGINAL_FILENAME
|
||||
VALUE "ProductName", PRODUCT_NAME
|
||||
VALUE "ProductVersion", PRODUCT_VERSION_STRING
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
|
||||
END
|
||||
END
|
||||
@@ -0,0 +1,208 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{2c3f7770-4e57-46b7-8dc1-7428a383d0db}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>GrabAndMoveModuleInterface</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>GrabAndMoveModuleInterface</ProjectName>
|
||||
<TargetName>PowerToys.GrabAndMoveModuleInterface</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<OutDir>$(RepoRoot)$(Platform)\$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;GRABANDMOVEMODULEINTERFACE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;GRABANDMOVEMODULEINTERFACE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;GRABANDMOVEMODULEINTERFACE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;GRABANDMOVEMODULEINTERFACE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(RepoRoot)src\common\inc;$(RepoRoot)src\common\Telemetry;..\..\;$(RepoRoot)src\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="trace.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<ClCompile Include="trace.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="GrabAndMoveModuleInterface.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(RepoRoot)src\common\logger\logger.vcxproj">
|
||||
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(RepoRoot)src\common\SettingsAPI\SettingsAPI.vcxproj">
|
||||
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="$(RepoRoot)deps\spdlog.props" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
216
src/modules/GrabAndMove/GrabAndMoveModuleInterface/dllmain.cpp
Normal file
216
src/modules/GrabAndMove/GrabAndMoveModuleInterface/dllmain.cpp
Normal file
@@ -0,0 +1,216 @@
|
||||
#include "pch.h"
|
||||
#include <interface/powertoy_module_interface.h>
|
||||
#include "trace.h"
|
||||
#include <common/logger/logger.h>
|
||||
#include <common/SettingsAPI/settings_objects.h>
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include <common/interop/shared_constants.h>
|
||||
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
Trace::RegisterProvider();
|
||||
break;
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
Trace::UnregisterProvider();
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// The PowerToy name that will be shown in the settings.
|
||||
const static wchar_t* MODULE_NAME = L"GrabAndMove";
|
||||
// Add a description that will be shown in the module settings page.
|
||||
const static wchar_t* MODULE_DESC = L"Move and resize windows with Alt+Drag (left button to move, right button to resize).";
|
||||
|
||||
class GrabAndMoveInterface : public PowertoyModuleIface
|
||||
{
|
||||
private:
|
||||
bool m_enabled = false;
|
||||
HANDLE m_process{ nullptr };
|
||||
HANDLE m_reload_settings_event_handle{ nullptr };
|
||||
HANDLE m_exit_event_handle{ nullptr };
|
||||
|
||||
public:
|
||||
GrabAndMoveInterface()
|
||||
{
|
||||
LoggerHelpers::init_logger(L"GrabAndMove", L"ModuleInterface", LogSettings::grabAndMoveLoggerName);
|
||||
m_reload_settings_event_handle = CreateDefaultEvent(CommonSharedConstants::GRABANDMOVE_REFRESH_SETTINGS_EVENT);
|
||||
m_exit_event_handle = CreateDefaultEvent(CommonSharedConstants::GRABANDMOVE_EXIT_EVENT);
|
||||
}
|
||||
|
||||
virtual const wchar_t* get_key() override
|
||||
{
|
||||
return L"GrabAndMove";
|
||||
}
|
||||
|
||||
virtual void destroy() override
|
||||
{
|
||||
disable();
|
||||
delete this;
|
||||
}
|
||||
|
||||
virtual const wchar_t* get_name() override
|
||||
{
|
||||
return MODULE_NAME;
|
||||
}
|
||||
|
||||
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
|
||||
{
|
||||
return powertoys_gpo::getConfiguredGrabAndMoveEnabledValue();
|
||||
}
|
||||
|
||||
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
|
||||
{
|
||||
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
||||
|
||||
PowerToysSettings::Settings settings(hinstance, get_name());
|
||||
settings.set_description(MODULE_DESC);
|
||||
settings.set_overview_link(L"https://aka.ms/powertoys");
|
||||
|
||||
return settings.serialize_to_buffer(buffer, buffer_size);
|
||||
}
|
||||
|
||||
virtual void set_config(const wchar_t* config) override
|
||||
{
|
||||
try
|
||||
{
|
||||
auto values = PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
|
||||
values.save_to_settings_file();
|
||||
|
||||
// Signal the GrabAndMove process to reload settings
|
||||
if (m_reload_settings_event_handle)
|
||||
{
|
||||
SetEvent(m_reload_settings_event_handle);
|
||||
}
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
Logger::error("[GrabAndMove] set_config: Failed to parse or apply config.");
|
||||
}
|
||||
}
|
||||
|
||||
virtual void enable()
|
||||
{
|
||||
Logger::info(L"Enabling GrabAndMove module...");
|
||||
|
||||
if (m_process && WaitForSingleObject(m_process, 0) == WAIT_TIMEOUT)
|
||||
{
|
||||
m_enabled = true;
|
||||
Trace::Enable(true);
|
||||
Logger::debug(L"GrabAndMove process already running.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_process)
|
||||
{
|
||||
CloseHandle(m_process);
|
||||
m_process = nullptr;
|
||||
}
|
||||
|
||||
m_enabled = false;
|
||||
|
||||
unsigned long powertoys_pid = GetCurrentProcessId();
|
||||
std::wstring args = std::to_wstring(powertoys_pid);
|
||||
std::wstring exe_name = L"PowerToys.GrabAndMove.exe";
|
||||
|
||||
std::wstring resolved_path(MAX_PATH, L'\0');
|
||||
DWORD result = SearchPathW(
|
||||
nullptr,
|
||||
exe_name.c_str(),
|
||||
nullptr,
|
||||
static_cast<DWORD>(resolved_path.size()),
|
||||
resolved_path.data(),
|
||||
nullptr);
|
||||
|
||||
if (result == 0 || result >= resolved_path.size())
|
||||
{
|
||||
Logger::error(
|
||||
L"Failed to locate GrabAndMove executable named '{}' at location '{}'",
|
||||
exe_name,
|
||||
resolved_path.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
resolved_path.resize(result);
|
||||
Logger::debug(L"Resolved executable path: {}", resolved_path);
|
||||
|
||||
std::wstring command_line = L"\"" + resolved_path + L"\" " + args;
|
||||
|
||||
STARTUPINFO si = { sizeof(si) };
|
||||
PROCESS_INFORMATION pi;
|
||||
|
||||
if (!CreateProcessW(
|
||||
resolved_path.c_str(),
|
||||
command_line.data(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
TRUE,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&si,
|
||||
&pi))
|
||||
{
|
||||
Logger::error(L"Failed to launch GrabAndMove process. {}", get_last_error_or_default(GetLastError()));
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::info(L"GrabAndMove process launched successfully (PID: {}).", pi.dwProcessId);
|
||||
m_process = pi.hProcess;
|
||||
m_enabled = true;
|
||||
Trace::Enable(true);
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
|
||||
virtual void disable()
|
||||
{
|
||||
Logger::info("GrabAndMove disabling");
|
||||
m_enabled = false;
|
||||
|
||||
if (m_exit_event_handle)
|
||||
{
|
||||
SetEvent(m_exit_event_handle);
|
||||
}
|
||||
|
||||
if (m_process)
|
||||
{
|
||||
constexpr DWORD timeout_ms = 1500;
|
||||
DWORD result = WaitForSingleObject(m_process, timeout_ms);
|
||||
|
||||
if (result == WAIT_TIMEOUT)
|
||||
{
|
||||
Logger::warn("GrabAndMove: Process didn't exit in time. Forcing termination.");
|
||||
TerminateProcess(m_process, 0);
|
||||
}
|
||||
|
||||
CloseHandle(m_process);
|
||||
m_process = nullptr;
|
||||
}
|
||||
|
||||
Trace::Enable(false);
|
||||
}
|
||||
|
||||
virtual bool is_enabled() override
|
||||
{
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
virtual bool is_enabled_by_default() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
|
||||
{
|
||||
return new GrabAndMoveInterface();
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
#include "pch.h"
|
||||
9
src/modules/GrabAndMove/GrabAndMoveModuleInterface/pch.h
Normal file
9
src/modules/GrabAndMove/GrabAndMoveModuleInterface/pch.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
#include <common/utils/gpo.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
#include <shlwapi.h>
|
||||
#include <shellapi.h>
|
||||
@@ -0,0 +1,12 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
|
||||
//////////////////////////////
|
||||
// Non-localizable
|
||||
|
||||
#define FILE_DESCRIPTION "GrabAndMove Module"
|
||||
#define INTERNAL_NAME "GrabAndMove"
|
||||
#define ORIGINAL_FILENAME "PowerToys.GrabAndMoveModuleInterface.dll"
|
||||
|
||||
// Non-localizable
|
||||
//////////////////////////////
|
||||
30
src/modules/GrabAndMove/GrabAndMoveModuleInterface/trace.cpp
Normal file
30
src/modules/GrabAndMove/GrabAndMoveModuleInterface/trace.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "pch.h"
|
||||
#include "trace.h"
|
||||
#include <TraceLoggingProvider.h>
|
||||
|
||||
TRACELOGGING_DEFINE_PROVIDER(
|
||||
g_hProvider,
|
||||
"Microsoft.PowerToys",
|
||||
// {38e8889b-9731-53f5-e901-e8a7c1753074}
|
||||
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
|
||||
TraceLoggingOptionProjectTelemetry());
|
||||
|
||||
void Trace::RegisterProvider()
|
||||
{
|
||||
TraceLoggingRegister(g_hProvider);
|
||||
}
|
||||
|
||||
void Trace::UnregisterProvider()
|
||||
{
|
||||
TraceLoggingUnregister(g_hProvider);
|
||||
}
|
||||
|
||||
void Trace::Enable(bool enabled) noexcept
|
||||
{
|
||||
TraceLoggingWrite(
|
||||
g_hProvider,
|
||||
"GrabAndMove_EnableGrabAndMove",
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
|
||||
TraceLoggingBoolean(enabled, "Enabled"));
|
||||
}
|
||||
15
src/modules/GrabAndMove/GrabAndMoveModuleInterface/trace.h
Normal file
15
src/modules/GrabAndMove/GrabAndMoveModuleInterface/trace.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <TraceLoggingActivity.h>
|
||||
#include <common/telemetry/ProjectTelemetry.h>
|
||||
|
||||
TRACELOGGING_DECLARE_PROVIDER(g_hProvider);
|
||||
|
||||
class Trace
|
||||
{
|
||||
public:
|
||||
static void RegisterProvider();
|
||||
static void UnregisterProvider();
|
||||
static void Enable(bool enabled) noexcept;
|
||||
};
|
||||
@@ -19,6 +19,7 @@ using Awake.Core;
|
||||
using Awake.Core.Models;
|
||||
using Awake.Core.Native;
|
||||
using Awake.Properties;
|
||||
using Awake.Telemetry;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
@@ -68,6 +69,7 @@ namespace Awake
|
||||
if (parseResult.Errors.Count > 0)
|
||||
{
|
||||
// Shows errors and returns non-zero.
|
||||
LogCLITelemetry(successful: false);
|
||||
return rootCommand.Invoke(args);
|
||||
}
|
||||
|
||||
@@ -96,6 +98,7 @@ namespace Awake
|
||||
{
|
||||
// Awake is already running - there is no need for us to process
|
||||
// anything further
|
||||
LogCLITelemetry(successful: false);
|
||||
Exit(Core.Constants.AppName + " is already running! Exiting the application.", 1);
|
||||
return 1;
|
||||
}
|
||||
@@ -103,6 +106,7 @@ namespace Awake
|
||||
{
|
||||
if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredAwakeEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
|
||||
{
|
||||
LogCLITelemetry(successful: false);
|
||||
Exit("PowerToys.Awake tried to start with a group policy setting that disables the tool. Please contact your system administrator.", 1);
|
||||
return 1;
|
||||
}
|
||||
@@ -125,7 +129,9 @@ namespace Awake
|
||||
Bridge.GetPwrCapabilities(out _powerCapabilities);
|
||||
Logger.LogInfo(JsonSerializer.Serialize(_powerCapabilities, _serializerOptions));
|
||||
|
||||
return await rootCommand.InvokeAsync(args);
|
||||
var result = await rootCommand.InvokeAsync(args);
|
||||
LogCLITelemetry(successful: result == 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -216,6 +222,22 @@ namespace Awake
|
||||
return rootCommand;
|
||||
}
|
||||
|
||||
private static void LogCLITelemetry(bool successful)
|
||||
{
|
||||
try
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new AwakeCLICommandEvent
|
||||
{
|
||||
CommandName = "awake",
|
||||
Successful = successful,
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to log CLI telemetry: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AwakeUnhandledExceptionCatcher(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
if (e.ExceptionObject is Exception exception)
|
||||
|
||||
37
src/modules/awake/Awake/Telemetry/AwakeCLICommandEvent.cs
Normal file
37
src/modules/awake/Awake/Telemetry/AwakeCLICommandEvent.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Awake.Telemetry
|
||||
{
|
||||
/// <summary>
|
||||
/// Telemetry event for Awake CLI command execution.
|
||||
/// </summary>
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class AwakeCLICommandEvent : EventBase, IEvent
|
||||
{
|
||||
public AwakeCLICommandEvent()
|
||||
{
|
||||
EventName = "Awake_CLICommand";
|
||||
CommandName = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the CLI command that was executed.
|
||||
/// </summary>
|
||||
public string CommandName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the command executed successfully.
|
||||
/// </summary>
|
||||
public bool Successful { get; set; }
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,9 @@ using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
using ImageResizer.Cli;
|
||||
using ImageResizer.Cli.Telemetry;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
|
||||
namespace ImageResizerCLI;
|
||||
|
||||
@@ -37,14 +39,33 @@ internal static class Program
|
||||
try
|
||||
{
|
||||
var executor = new ImageResizerCliExecutor();
|
||||
return executor.Run(args);
|
||||
int result = executor.Run(args);
|
||||
LogCLITelemetry(executor.CommandName, result == 0);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CliLogger.Error($"Unhandled exception: {ex.Message}");
|
||||
CliLogger.Error($"Stack trace: {ex.StackTrace}");
|
||||
Console.Error.WriteLine($"Fatal error: {ex.Message}");
|
||||
LogCLITelemetry("resize", successful: false);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void LogCLITelemetry(string commandName, bool successful)
|
||||
{
|
||||
try
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new ImageResizerCLICommandEvent
|
||||
{
|
||||
CommandName = commandName,
|
||||
Successful = successful,
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CliLogger.Error($"Failed to log CLI telemetry: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ namespace ImageResizer.Cli
|
||||
/// </summary>
|
||||
public class ImageResizerCliExecutor
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the last CLI operation that was executed.
|
||||
/// </summary>
|
||||
public string CommandName { get; private set; } = "resize";
|
||||
|
||||
/// <summary>
|
||||
/// Runs the CLI executor with the provided command-line arguments.
|
||||
/// </summary>
|
||||
@@ -37,18 +42,21 @@ namespace ImageResizer.Cli
|
||||
}
|
||||
|
||||
CliOptions.PrintUsage();
|
||||
CommandName = "error";
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (cliOptions.ShowHelp)
|
||||
{
|
||||
CliOptions.PrintUsage();
|
||||
CommandName = "help";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cliOptions.ShowConfig)
|
||||
{
|
||||
CliOptions.PrintConfig(Settings.Default);
|
||||
CommandName = "show-config";
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -56,6 +64,7 @@ namespace ImageResizer.Cli
|
||||
{
|
||||
Console.WriteLine(Resources.CLI_NoInputFiles);
|
||||
CliOptions.PrintUsage();
|
||||
CommandName = "error";
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace ImageResizer.Cli.Telemetry
|
||||
{
|
||||
/// <summary>
|
||||
/// Telemetry event for Image Resizer CLI command execution.
|
||||
/// </summary>
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class ImageResizerCLICommandEvent : EventBase, IEvent
|
||||
{
|
||||
public ImageResizerCLICommandEvent()
|
||||
{
|
||||
EventName = "ImageResizer_CLICommand";
|
||||
CommandName = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the CLI command that was executed.
|
||||
/// </summary>
|
||||
public string CommandName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the command executed successfully.
|
||||
/// </summary>
|
||||
public bool Successful { get; set; }
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ namespace ImageResizer.Models
|
||||
_scaleFormat ??= CompositeFormat.Parse(ResourceLoaderInstance.GetString("Input_AiScaleFormat"));
|
||||
|
||||
[ObservableProperty]
|
||||
[JsonPropertyName("scale")]
|
||||
[property: JsonPropertyName("scale")]
|
||||
private int _scale = 2;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -26,26 +26,26 @@ namespace ImageResizer.Models
|
||||
};
|
||||
|
||||
[ObservableProperty]
|
||||
[JsonPropertyName("Id")]
|
||||
[property: JsonPropertyName("Id")]
|
||||
private int _id;
|
||||
|
||||
private string _name;
|
||||
|
||||
[ObservableProperty]
|
||||
[JsonPropertyName("fit")]
|
||||
[property: JsonPropertyName("fit")]
|
||||
[NotifyPropertyChangedFor(nameof(ShowHeight))]
|
||||
private ResizeFit _fit = ResizeFit.Fit;
|
||||
|
||||
[ObservableProperty]
|
||||
[JsonPropertyName("width")]
|
||||
[property: JsonPropertyName("width")]
|
||||
private double _width;
|
||||
|
||||
[ObservableProperty]
|
||||
[JsonPropertyName("height")]
|
||||
[property: JsonPropertyName("height")]
|
||||
private double _height;
|
||||
|
||||
[ObservableProperty]
|
||||
[JsonPropertyName("unit")]
|
||||
[property: JsonPropertyName("unit")]
|
||||
[NotifyPropertyChangedFor(nameof(ShowHeight))]
|
||||
private ResizeUnit _unit = ResizeUnit.Pixel;
|
||||
|
||||
|
||||
@@ -471,10 +471,19 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
{
|
||||
InitializeContrast(monitor, candidate.Handle);
|
||||
}
|
||||
|
||||
// Initialize volume if supported
|
||||
if (monitor.SupportsVolume)
|
||||
{
|
||||
InitializeVolume(monitor, candidate.Handle);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize brightness (always supported for DDC/CI monitors)
|
||||
InitializeBrightness(monitor, candidate.Handle);
|
||||
// Initialize brightness if supported
|
||||
if (monitor.SupportsBrightness)
|
||||
{
|
||||
InitializeBrightness(monitor, candidate.Handle);
|
||||
}
|
||||
|
||||
monitors.Add(monitor);
|
||||
newHandleMap[monitor.Id] = candidate.Handle;
|
||||
@@ -541,6 +550,18 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize volume value for a monitor using VCP 0x62.
|
||||
/// </summary>
|
||||
private static void InitializeVolume(Monitor monitor, IntPtr handle)
|
||||
{
|
||||
if (TryGetVcpFeature(handle, VcpCodeVolume, monitor.Id, out uint current, out uint max))
|
||||
{
|
||||
var volumeInfo = new VcpFeatureValue((int)current, 0, (int)max);
|
||||
monitor.CurrentVolume = volumeInfo.ToPercentage();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for GetVCPFeatureAndVCPFeatureReply that logs errors on failure.
|
||||
/// </summary>
|
||||
@@ -568,6 +589,12 @@ namespace PowerDisplay.Common.Drivers.DDC
|
||||
/// </summary>
|
||||
private static void UpdateMonitorCapabilitiesFromVcp(Monitor monitor, VcpCapabilities vcpCaps)
|
||||
{
|
||||
// Check for Brightness support (VCP 0x10)
|
||||
if (vcpCaps.SupportsVcpCode(VcpCodeBrightness))
|
||||
{
|
||||
monitor.Capabilities |= MonitorCapabilities.Brightness;
|
||||
}
|
||||
|
||||
// Check for Contrast support (VCP 0x12)
|
||||
if (vcpCaps.SupportsVcpCode(VcpCodeContrast))
|
||||
{
|
||||
|
||||
@@ -165,6 +165,11 @@ namespace PowerDisplay.Common.Models
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the monitor supports brightness adjustment
|
||||
/// </summary>
|
||||
public bool SupportsBrightness => Capabilities.HasFlag(MonitorCapabilities.Brightness);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the monitor supports contrast adjustment
|
||||
/// </summary>
|
||||
|
||||
@@ -14,28 +14,28 @@ namespace PowerDisplay.Common.Models
|
||||
public sealed class MonitorStateEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the brightness level (0-100).
|
||||
/// Gets or sets the brightness level (0-100), or <c>null</c> if not yet read from the monitor.
|
||||
/// </summary>
|
||||
[JsonPropertyName("brightness")]
|
||||
public int Brightness { get; set; }
|
||||
public int? Brightness { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color temperature VCP value.
|
||||
/// Gets or sets the color temperature VCP value, or <c>null</c> if not yet read from the monitor.
|
||||
/// </summary>
|
||||
[JsonPropertyName("colorTemperature")]
|
||||
public int ColorTemperatureVcp { get; set; }
|
||||
public int? ColorTemperatureVcp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the contrast level (0-100).
|
||||
/// Gets or sets the contrast level (0-100), or <c>null</c> if not yet read from the monitor.
|
||||
/// </summary>
|
||||
[JsonPropertyName("contrast")]
|
||||
public int Contrast { get; set; }
|
||||
public int? Contrast { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the volume level (0-100).
|
||||
/// Gets or sets the volume level (0-100), or <c>null</c> if not yet read from the monitor.
|
||||
/// </summary>
|
||||
[JsonPropertyName("volume")]
|
||||
public int Volume { get; set; }
|
||||
public int? Volume { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the raw capabilities string from DDC/CI.
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace PowerDisplay.Common.Serialization
|
||||
/// </summary>
|
||||
[JsonSourceGenerationOptions(
|
||||
WriteIndented = false,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
IncludeFields = true)]
|
||||
[JsonSerializable(typeof(MonitorStateFile))]
|
||||
[JsonSerializable(typeof(MonitorStateEntry))]
|
||||
|
||||
@@ -35,13 +35,13 @@ namespace PowerDisplay.Common.Services
|
||||
/// </summary>
|
||||
private sealed class MonitorState
|
||||
{
|
||||
public int Brightness { get; set; }
|
||||
public int? Brightness { get; set; }
|
||||
|
||||
public int ColorTemperatureVcp { get; set; }
|
||||
public int? ColorTemperatureVcp { get; set; }
|
||||
|
||||
public int Contrast { get; set; }
|
||||
public int? Contrast { get; set; }
|
||||
|
||||
public int Volume { get; set; }
|
||||
public int? Volume { get; set; }
|
||||
|
||||
public string? CapabilitiesRaw { get; set; }
|
||||
}
|
||||
@@ -128,7 +128,7 @@ namespace PowerDisplay.Common.Services
|
||||
/// </summary>
|
||||
/// <param name="monitorId">The monitor's unique Id (e.g., "DDC_GSM5C6D_1").</param>
|
||||
/// <returns>A tuple of (Brightness, ColorTemperatureVcp, Contrast, Volume) or null if not found.</returns>
|
||||
public (int Brightness, int ColorTemperatureVcp, int Contrast, int Volume)? GetMonitorParameters(string monitorId)
|
||||
public (int? Brightness, int? ColorTemperatureVcp, int? Contrast, int? Volume)? GetMonitorParameters(string monitorId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(monitorId))
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Windowing;
|
||||
using PowerDisplay.Configuration;
|
||||
using PowerDisplay.Helpers;
|
||||
@@ -17,6 +17,8 @@ namespace PowerDisplay.PowerDisplayXAML
|
||||
public sealed partial class IdentifyWindow : WindowEx, IDisposable
|
||||
{
|
||||
private DpiSuppressor? _dpiSuppressor;
|
||||
private DispatcherQueueTimer? _autoCloseTimer;
|
||||
private bool _disposed;
|
||||
|
||||
public IdentifyWindow(string displayText)
|
||||
{
|
||||
@@ -40,14 +42,19 @@ namespace PowerDisplay.PowerDisplayXAML
|
||||
// Ensure DpiSuppressor is disposed when window closes
|
||||
this.Closed += (_, _) => Dispose();
|
||||
|
||||
// Auto close after 3 seconds
|
||||
Task.Delay(3000).ContinueWith(_ =>
|
||||
// Auto close after 3 seconds. DispatcherQueueTimer runs on the UI thread
|
||||
// and can be deterministically cancelled on Dispose, unlike a detached Task.Delay.
|
||||
_autoCloseTimer = DispatcherQueue.CreateTimer();
|
||||
_autoCloseTimer.Interval = TimeSpan.FromSeconds(3);
|
||||
_autoCloseTimer.IsRepeating = false;
|
||||
_autoCloseTimer.Tick += (_, _) =>
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
if (!_disposed)
|
||||
{
|
||||
Close();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
_autoCloseTimer.Start();
|
||||
}
|
||||
|
||||
private void ConfigureWindow()
|
||||
@@ -96,6 +103,15 @@ namespace PowerDisplay.PowerDisplayXAML
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
|
||||
_autoCloseTimer?.Stop();
|
||||
_autoCloseTimer = null;
|
||||
_dpiSuppressor?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,18 @@ namespace PowerDisplay.ViewModels;
|
||||
/// </summary>
|
||||
public partial class MainViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if a value is within the valid range (inclusive).
|
||||
/// </summary>
|
||||
private static bool IsValueInRange(int value, int min, int max) => value >= min && value <= max;
|
||||
private static void TryRestore(
|
||||
List<Task> tasks,
|
||||
int? savedValue,
|
||||
bool isVisible,
|
||||
int currentValue,
|
||||
Func<int, Task> setter)
|
||||
{
|
||||
if (savedValue.HasValue && isVisible && savedValue.Value != currentValue)
|
||||
{
|
||||
tasks.Add(setter(savedValue.Value));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply settings changes from Settings UI (IPC event handler entry point)
|
||||
@@ -212,32 +220,16 @@ public partial class MainViewModel
|
||||
}
|
||||
|
||||
// Apply brightness if included in profile
|
||||
if (setting.Brightness.HasValue &&
|
||||
IsValueInRange(setting.Brightness.Value, monitorVm.MinBrightness, monitorVm.MaxBrightness))
|
||||
{
|
||||
updateTasks.Add(monitorVm.SetBrightnessAsync(setting.Brightness.Value));
|
||||
}
|
||||
TryRestore(updateTasks, setting.Brightness, monitorVm.ShowBrightness, monitorVm.Brightness, monitorVm.SetBrightnessAsync);
|
||||
|
||||
// Apply contrast if supported and value provided
|
||||
if (setting.Contrast.HasValue && monitorVm.ShowContrast &&
|
||||
IsValueInRange(setting.Contrast.Value, monitorVm.MinContrast, monitorVm.MaxContrast))
|
||||
{
|
||||
updateTasks.Add(monitorVm.SetContrastAsync(setting.Contrast.Value));
|
||||
}
|
||||
TryRestore(updateTasks, setting.Contrast, monitorVm.ShowContrast, monitorVm.Contrast, monitorVm.SetContrastAsync);
|
||||
|
||||
// Apply volume if supported and value provided
|
||||
if (setting.Volume.HasValue && monitorVm.ShowVolume &&
|
||||
IsValueInRange(setting.Volume.Value, monitorVm.MinVolume, monitorVm.MaxVolume))
|
||||
{
|
||||
updateTasks.Add(monitorVm.SetVolumeAsync(setting.Volume.Value));
|
||||
}
|
||||
TryRestore(updateTasks, setting.Volume, monitorVm.ShowVolume, monitorVm.Volume, monitorVm.SetVolumeAsync);
|
||||
|
||||
// Apply color temperature if included in profile
|
||||
if (setting.ColorTemperatureVcp.HasValue && setting.ColorTemperatureVcp.Value > 0 &&
|
||||
monitorVm.ShowColorTemperature)
|
||||
{
|
||||
updateTasks.Add(monitorVm.SetColorTemperatureAsync(setting.ColorTemperatureVcp.Value));
|
||||
}
|
||||
TryRestore(updateTasks, setting.ColorTemperatureVcp, monitorVm.ShowColorTemperature, monitorVm.ColorTemperature, monitorVm.SetColorTemperatureAsync);
|
||||
}
|
||||
|
||||
// Wait for all updates to complete
|
||||
@@ -266,36 +258,12 @@ public partial class MainViewModel
|
||||
continue;
|
||||
}
|
||||
|
||||
// Restore brightness if different from current
|
||||
if (IsValueInRange(savedState.Value.Brightness, monitorVm.MinBrightness, monitorVm.MaxBrightness) &&
|
||||
savedState.Value.Brightness != monitorVm.Brightness)
|
||||
{
|
||||
updateTasks.Add(monitorVm.SetBrightnessAsync(savedState.Value.Brightness));
|
||||
}
|
||||
var (brightness, colorTemp, contrast, volume) = savedState.Value;
|
||||
|
||||
// Restore color temperature if different from current
|
||||
if (monitorVm.ShowColorTemperature &&
|
||||
savedState.Value.ColorTemperatureVcp > 0 &&
|
||||
savedState.Value.ColorTemperatureVcp != monitorVm.ColorTemperature)
|
||||
{
|
||||
updateTasks.Add(monitorVm.SetColorTemperatureAsync(savedState.Value.ColorTemperatureVcp));
|
||||
}
|
||||
|
||||
// Restore contrast if different from current
|
||||
if (monitorVm.ShowContrast &&
|
||||
IsValueInRange(savedState.Value.Contrast, monitorVm.MinContrast, monitorVm.MaxContrast) &&
|
||||
savedState.Value.Contrast != monitorVm.Contrast)
|
||||
{
|
||||
updateTasks.Add(monitorVm.SetContrastAsync(savedState.Value.Contrast));
|
||||
}
|
||||
|
||||
// Restore volume if different from current
|
||||
if (monitorVm.ShowVolume &&
|
||||
IsValueInRange(savedState.Value.Volume, monitorVm.MinVolume, monitorVm.MaxVolume) &&
|
||||
savedState.Value.Volume != monitorVm.Volume)
|
||||
{
|
||||
updateTasks.Add(monitorVm.SetVolumeAsync(savedState.Value.Volume));
|
||||
}
|
||||
TryRestore(updateTasks, brightness, monitorVm.ShowBrightness, monitorVm.Brightness, monitorVm.SetBrightnessAsync);
|
||||
TryRestore(updateTasks, colorTemp, monitorVm.ShowColorTemperature, monitorVm.ColorTemperature, monitorVm.SetColorTemperatureAsync);
|
||||
TryRestore(updateTasks, contrast, monitorVm.ShowContrast, monitorVm.Contrast, monitorVm.SetContrastAsync);
|
||||
TryRestore(updateTasks, volume, monitorVm.ShowVolume, monitorVm.Volume, monitorVm.SetVolumeAsync);
|
||||
}
|
||||
|
||||
if (updateTasks.Count > 0)
|
||||
|
||||
@@ -228,10 +228,13 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
||||
// Format display text: single number for normal mode, "1|2" for mirror mode
|
||||
var displayText = string.Join("|", monitorNumbers);
|
||||
|
||||
// Create and position identify window
|
||||
// Create and position identify window.
|
||||
// Position before Activate so the window appears directly at the target
|
||||
// location — avoiding a visible flicker from the default spawn position
|
||||
// and skipping a WM_DPICHANGED round-trip when crossing DPI monitors.
|
||||
var identifyWindow = new IdentifyWindow(displayText);
|
||||
identifyWindow.Activate();
|
||||
identifyWindow.PositionOnDisplay(displayArea);
|
||||
identifyWindow.Activate();
|
||||
windowsCreated++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,9 @@ public partial class MonitorViewModel : ObservableObject, IDisposable
|
||||
public partial bool IsAvailable { get; set; }
|
||||
|
||||
// Visibility settings (controlled by Settings UI)
|
||||
[ObservableProperty]
|
||||
public partial bool ShowBrightness { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(HasAdvancedControls))]
|
||||
public partial bool ShowContrast { get; set; }
|
||||
@@ -111,12 +114,32 @@ public partial class MonitorViewModel : ObservableObject, IDisposable
|
||||
await ApplyPropertyToHardwareAsync(nameof(Volume), volume, _monitorManager.SetVolumeAsync);
|
||||
}
|
||||
|
||||
private bool IsDiscreteValueSupported(byte vcpCode, int value)
|
||||
{
|
||||
var vcpInfo = VcpCapabilitiesInfo;
|
||||
if (vcpInfo == null ||
|
||||
!vcpInfo.SupportedVcpCodes.TryGetValue(vcpCode, out var codeInfo) ||
|
||||
!codeInfo.HasDiscreteValues ||
|
||||
!codeInfo.SupportedValues.Contains(value))
|
||||
{
|
||||
Logger.LogWarning($"[{Id}] VCP 0x{vcpCode:X2} value 0x{value:X2} not in reported supported values, skipping");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unified method to apply color temperature with hardware update and state persistence.
|
||||
/// Always immediate (no debouncing for discrete preset values).
|
||||
/// </summary>
|
||||
public async Task SetColorTemperatureAsync(int colorTemperature)
|
||||
{
|
||||
if (!IsDiscreteValueSupported(0x14, colorTemperature))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _monitorManager.SetColorTemperatureAsync(Id, colorTemperature);
|
||||
@@ -193,6 +216,7 @@ public partial class MonitorViewModel : ObservableObject, IDisposable
|
||||
_monitor.PropertyChanged += OnMonitorPropertyChanged;
|
||||
|
||||
// Initialize Show properties based on hardware capabilities
|
||||
ShowBrightness = monitor.SupportsBrightness;
|
||||
ShowContrast = monitor.SupportsContrast;
|
||||
ShowVolume = monitor.SupportsVolume;
|
||||
ShowInputSource = monitor.SupportsInputSource;
|
||||
@@ -272,6 +296,8 @@ public partial class MonitorViewModel : ObservableObject, IDisposable
|
||||
/// </summary>
|
||||
public bool SupportsContrast => _monitor.SupportsContrast;
|
||||
|
||||
public bool SupportsBrightness => _monitor.SupportsBrightness;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this monitor supports volume control via VCP 0x62
|
||||
/// </summary>
|
||||
@@ -458,41 +484,23 @@ public partial class MonitorViewModel : ObservableObject, IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Standard MCCS color temperature presets (VCP 0x14 values) to use as fallback
|
||||
/// when the monitor doesn't report discrete values in its capabilities string.
|
||||
/// </summary>
|
||||
private static readonly int[] StandardColorTemperaturePresets = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09, 0x0A, 0x0B };
|
||||
|
||||
/// <summary>
|
||||
/// Refresh the list of available color temperature presets based on monitor capabilities
|
||||
/// Refresh the list of available color temperature presets based on monitor capabilities.
|
||||
/// Only values explicitly reported in the capabilities string are exposed — no MCCS standard fallback.
|
||||
/// </summary>
|
||||
private void RefreshAvailableColorPresets()
|
||||
{
|
||||
if (!SupportsColorTemperature)
|
||||
var vcpInfo = VcpCapabilitiesInfo;
|
||||
if (!SupportsColorTemperature ||
|
||||
vcpInfo == null ||
|
||||
!vcpInfo.SupportedVcpCodes.TryGetValue(0x14, out var colorTempInfo) ||
|
||||
!colorTempInfo.HasDiscreteValues)
|
||||
{
|
||||
_availableColorPresets = null;
|
||||
OnPropertyChanged(nameof(AvailableColorPresets));
|
||||
return;
|
||||
}
|
||||
|
||||
IEnumerable<int> presetValues;
|
||||
var vcpInfo = VcpCapabilitiesInfo;
|
||||
|
||||
// Try to get discrete values from capabilities string
|
||||
if (vcpInfo != null &&
|
||||
vcpInfo.SupportedVcpCodes.TryGetValue(0x14, out var colorTempInfo) &&
|
||||
colorTempInfo.HasDiscreteValues &&
|
||||
colorTempInfo.SupportedValues.Count > 0)
|
||||
{
|
||||
// Use values from capabilities string
|
||||
presetValues = colorTempInfo.SupportedValues;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to standard MCCS presets when capabilities don't list discrete values
|
||||
presetValues = StandardColorTemperaturePresets;
|
||||
}
|
||||
|
||||
_availableColorPresets = presetValues.Select(value => new ColorTemperatureItem
|
||||
_availableColorPresets = colorTempInfo.SupportedValues.Select(value => new ColorTemperatureItem
|
||||
{
|
||||
VcpValue = value,
|
||||
DisplayName = Common.Utils.VcpNames.GetValueName(0x14, value, _mainViewModel?.CustomVcpMappings, _monitor.Id) is string n ? $"{n} (0x{value:X2})" : $"0x{value:X2}",
|
||||
@@ -584,6 +592,11 @@ public partial class MonitorViewModel : ObservableObject, IDisposable
|
||||
/// </summary>
|
||||
public async Task SetInputSourceAsync(int inputSource)
|
||||
{
|
||||
if (!IsDiscreteValueSupported(0x60, inputSource))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _monitorManager.SetInputSourceAsync(Id, inputSource);
|
||||
@@ -670,6 +683,11 @@ public partial class MonitorViewModel : ObservableObject, IDisposable
|
||||
/// </summary>
|
||||
public async Task SetPowerStateAsync(int powerState)
|
||||
{
|
||||
if (!IsDiscreteValueSupported(0xD6, powerState))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _monitorManager.SetPowerStateAsync(Id, powerState);
|
||||
|
||||
@@ -287,6 +287,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
|
||||
L"PowerToys.ZoomItModuleInterface.dll",
|
||||
L"PowerToys.LightSwitchModuleInterface.dll",
|
||||
L"PowerToys.PowerDisplayModuleInterface.dll",
|
||||
L"PowerToys.GrabAndMoveModuleInterface.dll",
|
||||
};
|
||||
|
||||
for (auto moduleSubdir : knownModules)
|
||||
|
||||
@@ -807,6 +807,8 @@ std::string ESettingsWindowNames_to_string(ESettingsWindowNames value)
|
||||
return "ZoomIt";
|
||||
case ESettingsWindowNames::PowerDisplay:
|
||||
return "PowerDisplay";
|
||||
case ESettingsWindowNames::GrabAndMove:
|
||||
return "GrabAndMove";
|
||||
default:
|
||||
{
|
||||
Logger::error(L"Can't convert ESettingsWindowNames value={} to string", static_cast<int>(value));
|
||||
@@ -950,6 +952,10 @@ ESettingsWindowNames ESettingsWindowNames_from_string(std::string value)
|
||||
{
|
||||
return ESettingsWindowNames::PowerDisplay;
|
||||
}
|
||||
else if (value == "GrabAndMove")
|
||||
{
|
||||
return ESettingsWindowNames::GrabAndMove;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"Can't convert string value={} to ESettingsWindowNames", winrt::to_hstring(value));
|
||||
|
||||
@@ -37,6 +37,7 @@ enum class ESettingsWindowNames
|
||||
CmdPal,
|
||||
ZoomIt,
|
||||
PowerDisplay,
|
||||
GrabAndMove,
|
||||
};
|
||||
|
||||
std::string ESettingsWindowNames_to_string(ESettingsWindowNames value);
|
||||
|
||||
@@ -44,6 +44,7 @@ internal static class ModuleGpoHelper
|
||||
ModuleType.ShortcutGuide => GPOWrapper.GetConfiguredShortcutGuideEnabledValue(),
|
||||
ModuleType.PowerOCR => GPOWrapper.GetConfiguredTextExtractorEnabledValue(),
|
||||
ModuleType.ZoomIt => GPOWrapper.GetConfiguredZoomItEnabledValue(),
|
||||
ModuleType.GrabAndMove => GPOWrapper.GetConfiguredGrabAndMoveEnabledValue(),
|
||||
_ => GpoRuleConfigured.Unavailable,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
}
|
||||
}
|
||||
|
||||
private bool powerLauncher = true;
|
||||
private bool powerLauncher; // defaulting to off
|
||||
|
||||
[JsonPropertyName("PowerToys Run")]
|
||||
public bool PowerLauncher
|
||||
@@ -153,7 +153,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
}
|
||||
}
|
||||
|
||||
private bool cropAndLock = true;
|
||||
private bool cropAndLock; // defaulting to off
|
||||
|
||||
[JsonPropertyName("CropAndLock")]
|
||||
public bool CropAndLock
|
||||
@@ -315,7 +315,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
}
|
||||
}
|
||||
|
||||
private bool advancedPaste = true;
|
||||
private bool advancedPaste; // defaulting to off
|
||||
|
||||
[JsonPropertyName("AdvancedPaste")]
|
||||
public bool AdvancedPaste
|
||||
@@ -349,7 +349,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
}
|
||||
}
|
||||
|
||||
private bool hosts = true;
|
||||
private bool hosts; // defaulting to off
|
||||
|
||||
[JsonPropertyName("Hosts")]
|
||||
public bool Hosts
|
||||
@@ -398,7 +398,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
}
|
||||
}
|
||||
|
||||
private bool registryPreview = true;
|
||||
private bool registryPreview; // defaulting to off
|
||||
|
||||
[JsonPropertyName("RegistryPreview")]
|
||||
public bool RegistryPreview
|
||||
@@ -431,7 +431,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
}
|
||||
}
|
||||
|
||||
private bool environmentVariables = true;
|
||||
private bool environmentVariables; // defaulting to off
|
||||
|
||||
[JsonPropertyName("EnvironmentVariables")]
|
||||
public bool EnvironmentVariables
|
||||
@@ -463,7 +463,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
}
|
||||
}
|
||||
|
||||
private bool workspaces = true;
|
||||
private bool workspaces; // defaulting to off
|
||||
|
||||
[JsonPropertyName("Workspaces")]
|
||||
public bool Workspaces
|
||||
@@ -563,6 +563,23 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
}
|
||||
}
|
||||
|
||||
private bool grabAndMove;
|
||||
|
||||
[JsonPropertyName("GrabAndMove")]
|
||||
public bool GrabAndMove
|
||||
{
|
||||
get => grabAndMove;
|
||||
set
|
||||
{
|
||||
if (grabAndMove != value)
|
||||
{
|
||||
LogTelemetryEvent(value);
|
||||
grabAndMove = value;
|
||||
NotifyChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void NotifyChange()
|
||||
{
|
||||
notifyEnabledChangedAction?.Invoke();
|
||||
|
||||
35
src/settings-ui/Settings.UI.Library/GrabAndMoveProperties.cs
Normal file
35
src/settings-ui/Settings.UI.Library/GrabAndMoveProperties.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class GrabAndMoveProperties
|
||||
{
|
||||
public GrabAndMoveProperties()
|
||||
{
|
||||
ShouldAbsorbAlt = new BoolProperty(true);
|
||||
DoNotActivateOnGameMode = new BoolProperty(true);
|
||||
ShowGeometry = new BoolProperty(false);
|
||||
UseAltResize = new BoolProperty(true);
|
||||
ExcludedApps = new StringProperty();
|
||||
}
|
||||
|
||||
[JsonPropertyName("shouldAbsorbAlt")]
|
||||
public BoolProperty ShouldAbsorbAlt { get; set; }
|
||||
|
||||
[JsonPropertyName("showGeometry")]
|
||||
public BoolProperty ShowGeometry { get; set; }
|
||||
|
||||
[JsonPropertyName("useAltResize")]
|
||||
public BoolProperty UseAltResize { get; set; }
|
||||
|
||||
[JsonPropertyName("doNotActivateOnGameMode")]
|
||||
public BoolProperty DoNotActivateOnGameMode { get; set; }
|
||||
|
||||
[JsonPropertyName("excluded_apps")]
|
||||
public StringProperty ExcludedApps { get; set; }
|
||||
}
|
||||
}
|
||||
32
src/settings-ui/Settings.UI.Library/GrabAndMoveSettings.cs
Normal file
32
src/settings-ui/Settings.UI.Library/GrabAndMoveSettings.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Text.Json.Serialization;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class GrabAndMoveSettings : BasePTModuleSettings, ISettingsConfig
|
||||
{
|
||||
public const string ModuleName = "GrabAndMove";
|
||||
|
||||
public GrabAndMoveSettings()
|
||||
{
|
||||
Name = ModuleName;
|
||||
Version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
Properties = new GrabAndMoveProperties();
|
||||
}
|
||||
|
||||
[JsonPropertyName("properties")]
|
||||
public GrabAndMoveProperties Properties { get; set; }
|
||||
|
||||
public string GetModuleName() => Name;
|
||||
|
||||
public bool UpgradeSettingsConfiguration() => false;
|
||||
|
||||
public ModuleType GetModuleType() => ModuleType.GrabAndMove;
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Helpers
|
||||
ModuleType.PowerOCR => generalSettingsConfig.Enabled.PowerOcr,
|
||||
ModuleType.PowerDisplay => generalSettingsConfig.Enabled.PowerDisplay,
|
||||
ModuleType.Workspaces => generalSettingsConfig.Enabled.Workspaces,
|
||||
ModuleType.GrabAndMove => generalSettingsConfig.Enabled.GrabAndMove,
|
||||
ModuleType.ZoomIt => generalSettingsConfig.Enabled.ZoomIt,
|
||||
ModuleType.GeneralSettings => generalSettingsConfig.EnableQuickAccess,
|
||||
_ => false,
|
||||
@@ -115,6 +116,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Helpers
|
||||
case ModuleType.PowerOCR: generalSettingsConfig.Enabled.PowerOcr = isEnabled; break;
|
||||
case ModuleType.PowerDisplay: generalSettingsConfig.Enabled.PowerDisplay = isEnabled; break;
|
||||
case ModuleType.Workspaces: generalSettingsConfig.Enabled.Workspaces = isEnabled; break;
|
||||
case ModuleType.GrabAndMove: generalSettingsConfig.Enabled.GrabAndMove = isEnabled; break;
|
||||
case ModuleType.ZoomIt: generalSettingsConfig.Enabled.ZoomIt = isEnabled; break;
|
||||
case ModuleType.GeneralSettings: generalSettingsConfig.EnableQuickAccess = isEnabled; break;
|
||||
}
|
||||
@@ -158,6 +160,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Helpers
|
||||
ModuleType.ShortcutGuide => ShortcutGuideSettings.ModuleName,
|
||||
ModuleType.PowerOCR => PowerOcrSettings.ModuleName,
|
||||
ModuleType.Workspaces => WorkspacesSettings.ModuleName,
|
||||
ModuleType.GrabAndMove => GrabAndMoveSettings.ModuleName,
|
||||
ModuleType.ZoomIt => ZoomItSettings.ModuleName,
|
||||
_ => moduleType.ToString(),
|
||||
};
|
||||
|
||||
@@ -75,6 +75,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[JsonSerializable(typeof(RegistryPreviewSettings))]
|
||||
[JsonSerializable(typeof(ShortcutGuideSettings))]
|
||||
[JsonSerializable(typeof(WorkspacesSettings))]
|
||||
[JsonSerializable(typeof(GrabAndMoveSettings))]
|
||||
[JsonSerializable(typeof(ZoomItSettings))]
|
||||
|
||||
// Properties Classes
|
||||
@@ -115,6 +116,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[JsonSerializable(typeof(ShortcutConflictProperties))]
|
||||
[JsonSerializable(typeof(ShortcutGuideProperties))]
|
||||
[JsonSerializable(typeof(WorkspacesProperties))]
|
||||
[JsonSerializable(typeof(GrabAndMoveProperties))]
|
||||
[JsonSerializable(typeof(ZoomItProperties))]
|
||||
|
||||
// Base Property Types (used throughout settings)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class SndGrabAndMoveSettings
|
||||
{
|
||||
[JsonPropertyName("GrabAndMove")]
|
||||
public GrabAndMoveSettings Settings { get; set; }
|
||||
|
||||
public SndGrabAndMoveSettings()
|
||||
{
|
||||
}
|
||||
|
||||
public SndGrabAndMoveSettings(GrabAndMoveSettings settings)
|
||||
{
|
||||
Settings = settings;
|
||||
}
|
||||
|
||||
public string ToJsonString()
|
||||
{
|
||||
return JsonSerializer.Serialize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,16 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
}
|
||||
|
||||
public string LastCheckedDateLocalized
|
||||
{
|
||||
get
|
||||
{
|
||||
var dt = LastCheckedDateTime;
|
||||
return dt?.ToString(CultureInfo.CurrentCulture) ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public DateTime? LastCheckedDateTime
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -72,18 +82,18 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
if (LastCheckedDate == null)
|
||||
{
|
||||
return string.Empty;
|
||||
return null;
|
||||
}
|
||||
|
||||
long seconds = long.Parse(LastCheckedDate, CultureInfo.CurrentCulture);
|
||||
var date = DateTimeOffset.FromUnixTimeSeconds(seconds).UtcDateTime;
|
||||
return date.ToLocalTime().ToString(CultureInfo.CurrentCulture);
|
||||
return date.ToLocalTime();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -351,7 +351,7 @@ namespace ViewModelTests
|
||||
Assert.IsTrue(modules.PowerPreview);
|
||||
Assert.IsTrue(modules.ShortcutGuide);
|
||||
Assert.IsTrue(modules.PowerRename);
|
||||
Assert.IsTrue(modules.PowerLauncher);
|
||||
Assert.IsFalse(modules.PowerLauncher);
|
||||
Assert.IsTrue(modules.ColorPicker);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 942 B |
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
48
src/settings-ui/Settings.UI/Helpers/FriendlyDateHelper.cs
Normal file
48
src/settings-ui/Settings.UI/Helpers/FriendlyDateHelper.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
|
||||
public static class FriendlyDateHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Formats a <see cref="DateTime"/> as a friendly relative string.
|
||||
/// Today → "Today at 1:22 PM", Yesterday → "Yesterday at 3:45 PM",
|
||||
/// older dates fall back to the full culture-specific date/time format.
|
||||
/// </summary>
|
||||
public static string Format(DateTime? dateTime)
|
||||
{
|
||||
if (dateTime is not DateTime dt)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
|
||||
var today = DateTime.Now.Date;
|
||||
var time = dt.ToString("t", CultureInfo.CurrentCulture);
|
||||
|
||||
if (dt.Date == today)
|
||||
{
|
||||
var fmt = resourceLoader.GetString("General_LastCheckedDate_TodayAt");
|
||||
if (!string.IsNullOrEmpty(fmt))
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, fmt, time);
|
||||
}
|
||||
}
|
||||
|
||||
if (dt.Date == today.AddDays(-1))
|
||||
{
|
||||
var fmt = resourceLoader.GetString("General_LastCheckedDate_YesterdayAt");
|
||||
if (!string.IsNullOrEmpty(fmt))
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, fmt, time);
|
||||
}
|
||||
}
|
||||
|
||||
return dt.ToString(CultureInfo.CurrentCulture);
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
case ModuleType.PowerOCR: return GPOWrapper.GetConfiguredTextExtractorEnabledValue();
|
||||
case ModuleType.PowerDisplay: return GPOWrapper.GetConfiguredPowerDisplayEnabledValue();
|
||||
case ModuleType.ZoomIt: return GPOWrapper.GetConfiguredZoomItEnabledValue();
|
||||
case ModuleType.GrabAndMove: return GPOWrapper.GetConfiguredGrabAndMoveEnabledValue();
|
||||
default: return GpoRuleConfigured.Unavailable;
|
||||
}
|
||||
}
|
||||
@@ -87,6 +88,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
ModuleType.PowerOCR => typeof(PowerOcrPage),
|
||||
ModuleType.PowerDisplay => typeof(PowerDisplayPage),
|
||||
ModuleType.ZoomIt => typeof(ZoomItPage),
|
||||
ModuleType.GrabAndMove => typeof(GrabAndMovePage),
|
||||
_ => typeof(DashboardPage), // never called, all values listed above
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Enums
|
||||
Workspaces,
|
||||
RegistryPreview,
|
||||
PowerDisplay,
|
||||
GrabAndMove,
|
||||
NewPlus,
|
||||
ZoomIt,
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.ViewModel
|
||||
(PowerToysModules.MeasureTool, false),
|
||||
(PowerToysModules.Hosts, false),
|
||||
(PowerToysModules.Workspaces, false),
|
||||
(PowerToysModules.GrabAndMove, false),
|
||||
(PowerToysModules.RegistryPreview, false),
|
||||
(PowerToysModules.NewPlus, false),
|
||||
(PowerToysModules.ZoomIt, false),
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
[JsonSerializable(typeof(ShortcutGuideSettings))]
|
||||
[JsonSerializable(typeof(WINDOWPLACEMENT))]
|
||||
[JsonSerializable(typeof(WorkspacesSettings))]
|
||||
[JsonSerializable(typeof(GrabAndMoveSettings))]
|
||||
[JsonSerializable(typeof(ZoomItSettings))]
|
||||
[JsonSerializable(typeof(PasteAIConfiguration))]
|
||||
[JsonSerializable(typeof(PasteAIProviderDefinition))]
|
||||
|
||||
@@ -447,6 +447,7 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
case "Workspaces": return typeof(WorkspacesPage);
|
||||
case "CmdPal": return typeof(CmdPalPage);
|
||||
case "ZoomIt": return typeof(ZoomItPage);
|
||||
case "GrabAndMove": return typeof(GrabAndMovePage);
|
||||
default:
|
||||
// Fallback to Dashboard
|
||||
Debug.Assert(false, "Unexpected SettingsWindow argument value");
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
<TextBlock x:Uid="YoureUpToDate" FontWeight="SemiBold" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Style="{StaticResource CaptionTextBlockStyle}">
|
||||
<Run x:Uid="General_VersionLastChecked" />
|
||||
<Run Text="{x:Bind UpdateSettingsConfig.LastCheckedDateLocalized, Mode=OneTime}" />
|
||||
<Run Text="{x:Bind LastCheckedDateFriendly, Mode=OneTime}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Services;
|
||||
using Microsoft.PowerToys.Settings.UI.Views;
|
||||
@@ -15,11 +16,14 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
|
||||
|
||||
public UpdatingSettings UpdateSettingsConfig { get; set; }
|
||||
|
||||
public string LastCheckedDateFriendly { get; set; }
|
||||
|
||||
public CheckUpdateControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
UpdateSettingsConfig = UpdatingSettings.LoadSettings();
|
||||
UpdateAvailable = UpdateSettingsConfig != null && (UpdateSettingsConfig.State == UpdatingSettings.UpdatingState.ReadyToInstall || UpdateSettingsConfig.State == UpdatingSettings.UpdatingState.ReadyToDownload);
|
||||
LastCheckedDateFriendly = FriendlyDateHelper.Format(UpdateSettingsConfig?.LastCheckedDateTime);
|
||||
}
|
||||
|
||||
private void SWVersionButtonClicked(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<local:NavigablePage
|
||||
x:Class="Microsoft.PowerToys.Settings.UI.Views.GrabAndMovePage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Helpers"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
AutomationProperties.LandmarkType="Main"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<controls:SettingsPageControl
|
||||
x:Uid="GrabAndMove"
|
||||
IsTabStop="False"
|
||||
ModuleImageSource="ms-appx:///Assets/Settings/Modules/GrabAndMove.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="GrabAndMoveEnableToggleControlHeaderText"
|
||||
x:Uid="GrabAndMove_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/GrabAndMove.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
|
||||
<controls:SettingsGroup x:Uid="GrabAndMove_BehaviorSettingsGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard x:Uid="GrabAndMove_ShouldAbsorbAlt" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.ShouldAbsorbAlt, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard x:Uid="GrabAndMove_UseAltResize" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.UseAltResize, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard x:Uid="GrabAndMove_DoNotActivateOnGameMode" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.DoNotActivateOnGameMode, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard x:Uid="GrabAndMove_ShowGeometry" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.ShowGeometry, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="ExcludedApps" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander
|
||||
Name="GrabAndMoveExcludeApps"
|
||||
x:Uid="GrabAndMove_ExcludeApps"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="True">
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard
|
||||
Name="GrabAndMoveExcludeAppsTextBoxControl"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
ContentAlignment="Vertical">
|
||||
<TextBox
|
||||
x:Uid="GrabAndMove_ExcludeApps_TextBoxControl"
|
||||
MinWidth="240"
|
||||
MinHeight="160"
|
||||
AcceptsReturn="True"
|
||||
ScrollViewer.IsVerticalRailEnabled="True"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Visible"
|
||||
ScrollViewer.VerticalScrollMode="Enabled"
|
||||
Text="{x:Bind ViewModel.ExcludedApps, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
TextWrapping="Wrap" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
</controls:SettingsGroup>
|
||||
</StackPanel>
|
||||
</controls:SettingsPageControl.ModuleContent>
|
||||
|
||||
<controls:SettingsPageControl.PrimaryLinks>
|
||||
<controls:PageLink x:Uid="LearnMore_GrabAndMove" Link="https://aka.ms/PowerToysOverview_GrabAndMove" />
|
||||
</controls:SettingsPageControl.PrimaryLinks>
|
||||
</controls:SettingsPageControl>
|
||||
</local:NavigablePage>
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.ViewModels;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
{
|
||||
public sealed partial class GrabAndMovePage : NavigablePage, IRefreshablePage
|
||||
{
|
||||
private GrabAndMoveViewModel ViewModel { get; set; }
|
||||
|
||||
public GrabAndMovePage()
|
||||
{
|
||||
var moduleSettingsRepository = SettingsRepository<GrabAndMoveSettings>.GetInstance(SettingsUtils.Default);
|
||||
ViewModel = new GrabAndMoveViewModel(
|
||||
SettingsRepository<GeneralSettings>.GetInstance(SettingsUtils.Default),
|
||||
moduleSettingsRepository.SettingsConfig,
|
||||
ShellPage.SendDefaultIPCMessage);
|
||||
DataContext = ViewModel;
|
||||
InitializeComponent();
|
||||
|
||||
Loaded += (s, e) => ViewModel.OnPageLoaded();
|
||||
}
|
||||
|
||||
public void RefreshEnabledState()
|
||||
{
|
||||
ViewModel.RefreshEnabledState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,6 +274,12 @@
|
||||
helpers:NavHelper.NavigateTo="views:WorkspacesPage"
|
||||
AutomationProperties.AutomationId="WorkspacesNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Workspaces.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="GrabAndMoveNavigationItem"
|
||||
x:Uid="Shell_GrabAndMove"
|
||||
helpers:NavHelper.NavigateTo="views:GrabAndMovePage"
|
||||
AutomationProperties.AutomationId="GrabAndMoveNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/GrabAndMove.png}" />
|
||||
</NavigationViewItem.MenuItems>
|
||||
</NavigationViewItem>
|
||||
|
||||
|
||||
@@ -1377,6 +1377,14 @@ opera.exe</value>
|
||||
<data name="General_VersionLastChecked.Text" xml:space="preserve">
|
||||
<value>Last checked: </value>
|
||||
</data>
|
||||
<data name="General_LastCheckedDate_TodayAt" xml:space="preserve">
|
||||
<value>Today at {0}</value>
|
||||
<comment>Friendly date format when the last update check was today. {0} is the localized short time, e.g. "Today at 1:22 PM".</comment>
|
||||
</data>
|
||||
<data name="General_LastCheckedDate_YesterdayAt" xml:space="preserve">
|
||||
<value>Yesterday at {0}</value>
|
||||
<comment>Friendly date format when the last update check was yesterday. {0} is the localized short time, e.g. "Yesterday at 3:45 PM".</comment>
|
||||
</data>
|
||||
<data name="General_SettingsBackupInfo_DateHeader.Text" xml:space="preserve">
|
||||
<value>Created at:</value>
|
||||
</data>
|
||||
@@ -5913,4 +5921,62 @@ Text uses the current drawing color.</value>
|
||||
<data name="ZoomIt_Type_DemoSample.Text" xml:space="preserve">
|
||||
<value>Sample</value>
|
||||
</data>
|
||||
<data name="GrabAndMove.ModuleDescription" xml:space="preserve">
|
||||
<value>Move and resize windows with Alt+Drag. Left-click to move, right-click to resize.</value>
|
||||
<comment>"Grab And Move" is the name of the utility</comment>
|
||||
</data>
|
||||
<data name="GrabAndMove.ModuleTitle" xml:space="preserve">
|
||||
<value>Grab And Move</value>
|
||||
<comment>"Grab And Move" is the name of the utility</comment>
|
||||
</data>
|
||||
<data name="Shell_GrabAndMove.Content" xml:space="preserve">
|
||||
<value>Grab And Move</value>
|
||||
<comment>"Grab And Move" is the name of the utility</comment>
|
||||
</data>
|
||||
<data name="GrabAndMove_EnableToggleControl_HeaderText.Header" xml:space="preserve">
|
||||
<value>Grab And Move</value>
|
||||
<comment>"Grab And Move" is the name of the utility</comment>
|
||||
</data>
|
||||
<data name="LearnMore_GrabAndMove.Text" xml:space="preserve">
|
||||
<value>Learn more about Grab And Move</value>
|
||||
<comment>"Grab And Move" is the name of the utility</comment>
|
||||
</data>
|
||||
<data name="GrabAndMove_BehaviorSettingsGroup.Header" xml:space="preserve">
|
||||
<value>Behavior</value>
|
||||
</data>
|
||||
<data name="GrabAndMove_ShouldAbsorbAlt.Header" xml:space="preserve">
|
||||
<value>Absorb Alt presses</value>
|
||||
<comment>When enabled, Grab And Move absorbs the Alt keypress so it does not activate the window menu after a drag/resize.</comment>
|
||||
</data>
|
||||
<data name="GrabAndMove_ShouldAbsorbAlt.Description" xml:space="preserve">
|
||||
<value>Prevent Alt from activating the window menu after a drag or resize operation</value>
|
||||
</data>
|
||||
<data name="GrabAndMove_DoNotActivateOnGameMode.Header" xml:space="preserve">
|
||||
<value>Do not activate when Game Mode is on</value>
|
||||
<comment>Game Mode is a Windows feature</comment>
|
||||
</data>
|
||||
<data name="GrabAndMove_ShowGeometry.Header" xml:space="preserve">
|
||||
<value>Show window geometry</value>
|
||||
<comment>When enabled, Grab And Move displays the window position (X, Y) and size (W, H) on the overlay during move and resize.</comment>
|
||||
</data>
|
||||
<data name="GrabAndMove_ShowGeometry.Description" xml:space="preserve">
|
||||
<value>Display the window position and size on the overlay during drag and resize operations</value>
|
||||
</data>
|
||||
<data name="GrabAndMove_UseAltResize.Header" xml:space="preserve">
|
||||
<value>Enable Alt + Right-click to resize</value>
|
||||
<comment>When enabled, holding Alt and right-clicking allows resizing windows by dragging from the closest edge or corner.</comment>
|
||||
</data>
|
||||
<data name="GrabAndMove_UseAltResize.Description" xml:space="preserve">
|
||||
<value>Hold Alt and right-click to resize a window by dragging from the nearest edge or corner</value>
|
||||
</data>
|
||||
<data name="GrabAndMove_ExcludeApps.Header" xml:space="preserve">
|
||||
<value>Excluded apps</value>
|
||||
</data>
|
||||
<data name="GrabAndMove_ExcludeApps.Description" xml:space="preserve">
|
||||
<value>Excludes an application from being moved or resized with Alt+Drag - add one application name per line</value>
|
||||
</data>
|
||||
<data name="GrabAndMove_ExcludeApps_TextBoxControl.PlaceholderText" xml:space="preserve">
|
||||
<value>Example: outlook.exe</value>
|
||||
<comment>{Locked="outlook.exe"}</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -502,6 +502,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
ModuleType.PowerLauncher => GetModuleItemsPowerLauncher(),
|
||||
ModuleType.PowerAccent => GetModuleItemsPowerAccent(),
|
||||
ModuleType.Workspaces => GetModuleItemsWorkspaces(),
|
||||
ModuleType.GrabAndMove => new ObservableCollection<DashboardModuleItem>(),
|
||||
ModuleType.RegistryPreview => GetModuleItemsRegistryPreview(),
|
||||
ModuleType.MeasureTool => GetModuleItemsMeasureTool(),
|
||||
ModuleType.ShortcutGuide => GetModuleItemsShortcutGuide(),
|
||||
|
||||
@@ -188,7 +188,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
_updatingState = UpdatingSettingsConfig.State;
|
||||
_newAvailableVersion = UpdatingSettingsConfig.NewVersion;
|
||||
_newAvailableVersionLink = UpdatingSettingsConfig.ReleasePageLink;
|
||||
_updateCheckedDate = UpdatingSettingsConfig.LastCheckedDateLocalized;
|
||||
_updateCheckedDate = FriendlyDateHelper.Format(UpdatingSettingsConfig.LastCheckedDateTime);
|
||||
|
||||
_newUpdatesToastIsGpoDisabled = GPOWrapper.GetDisableNewUpdateToastValue() == GpoRuleConfigured.Enabled;
|
||||
_autoDownloadUpdatesIsGpoDisabled = GPOWrapper.GetDisableAutomaticUpdateDownloadValue() == GpoRuleConfigured.Enabled;
|
||||
@@ -1383,7 +1383,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
else
|
||||
{
|
||||
bool dateChanged = UpdateCheckedDate == UpdatingSettingsConfig.LastCheckedDateLocalized;
|
||||
bool dateChanged = UpdateCheckedDate == FriendlyDateHelper.Format(UpdatingSettingsConfig.LastCheckedDateTime);
|
||||
bool fileDownloaded = string.IsNullOrEmpty(UpdatingSettingsConfig.DownloadedInstallerFilename);
|
||||
IsNewVersionDownloading = !(dateChanged || fileDownloaded);
|
||||
}
|
||||
@@ -1391,7 +1391,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
PowerToysUpdatingState = UpdatingSettingsConfig.State;
|
||||
PowerToysNewAvailableVersion = UpdatingSettingsConfig.NewVersion;
|
||||
PowerToysNewAvailableVersionLink = UpdatingSettingsConfig.ReleasePageLink;
|
||||
UpdateCheckedDate = UpdatingSettingsConfig.LastCheckedDateLocalized;
|
||||
UpdateCheckedDate = FriendlyDateHelper.Format(UpdatingSettingsConfig.LastCheckedDateTime);
|
||||
|
||||
_isNoNetwork = PowerToysUpdatingState == UpdatingSettings.UpdatingState.NetworkError;
|
||||
NotifyPropertyChanged(nameof(IsNoNetwork));
|
||||
|
||||
174
src/settings-ui/Settings.UI/ViewModels/GrabAndMoveViewModel.cs
Normal file
174
src/settings-ui/Settings.UI/ViewModels/GrabAndMoveViewModel.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using global::PowerToys.GPOWrapper;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
using Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
public partial class GrabAndMoveViewModel : PageViewModelBase
|
||||
{
|
||||
protected override string ModuleName => GrabAndMoveSettings.ModuleName;
|
||||
|
||||
private GeneralSettings GeneralSettingsConfig { get; set; }
|
||||
|
||||
private Func<string, int> SendConfigMSG { get; }
|
||||
|
||||
private GpoRuleConfigured _enabledGpoRuleConfiguration;
|
||||
private bool _enabledStateIsGPOConfigured;
|
||||
private bool _isEnabled;
|
||||
|
||||
private GrabAndMoveSettings _moduleSettings;
|
||||
|
||||
public GrabAndMoveSettings ModuleSettings => _moduleSettings;
|
||||
|
||||
public GrabAndMoveViewModel(ISettingsRepository<GeneralSettings> settingsRepository, GrabAndMoveSettings moduleSettings, Func<string, int> ipcMSGCallBackFunc)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(settingsRepository);
|
||||
|
||||
GeneralSettingsConfig = settingsRepository.SettingsConfig;
|
||||
_moduleSettings = moduleSettings ?? new GrabAndMoveSettings();
|
||||
|
||||
InitializeEnabledValue();
|
||||
|
||||
SendConfigMSG = ipcMSGCallBackFunc ?? (_ => 0);
|
||||
}
|
||||
|
||||
private void InitializeEnabledValue()
|
||||
{
|
||||
_enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredGrabAndMoveEnabledValue();
|
||||
if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
|
||||
{
|
||||
_enabledStateIsGPOConfigured = true;
|
||||
_isEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
_isEnabled = GeneralSettingsConfig.Enabled.GrabAndMove;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
|
||||
set
|
||||
{
|
||||
if (_enabledStateIsGPOConfigured)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (value != _isEnabled)
|
||||
{
|
||||
_isEnabled = value;
|
||||
GeneralSettingsConfig.Enabled.GrabAndMove = value;
|
||||
OutGoingGeneralSettings snd = new OutGoingGeneralSettings(GeneralSettingsConfig);
|
||||
SendConfigMSG(snd.ToString());
|
||||
OnPropertyChanged(nameof(IsEnabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabledGpoConfigured
|
||||
{
|
||||
get => _enabledStateIsGPOConfigured;
|
||||
}
|
||||
|
||||
public bool ShouldAbsorbAlt
|
||||
{
|
||||
get => _moduleSettings.Properties.ShouldAbsorbAlt.Value;
|
||||
|
||||
set
|
||||
{
|
||||
if (_moduleSettings.Properties.ShouldAbsorbAlt.Value != value)
|
||||
{
|
||||
_moduleSettings.Properties.ShouldAbsorbAlt.Value = value;
|
||||
NotifyModuleSettingsChanged();
|
||||
OnPropertyChanged(nameof(ShouldAbsorbAlt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowGeometry
|
||||
{
|
||||
get => _moduleSettings.Properties.ShowGeometry.Value;
|
||||
|
||||
set
|
||||
{
|
||||
if (_moduleSettings.Properties.ShowGeometry.Value != value)
|
||||
{
|
||||
_moduleSettings.Properties.ShowGeometry.Value = value;
|
||||
NotifyModuleSettingsChanged();
|
||||
OnPropertyChanged(nameof(ShowGeometry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseAltResize
|
||||
{
|
||||
get => _moduleSettings.Properties.UseAltResize.Value;
|
||||
|
||||
set
|
||||
{
|
||||
if (_moduleSettings.Properties.UseAltResize.Value != value)
|
||||
{
|
||||
_moduleSettings.Properties.UseAltResize.Value = value;
|
||||
NotifyModuleSettingsChanged();
|
||||
OnPropertyChanged(nameof(UseAltResize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool DoNotActivateOnGameMode
|
||||
{
|
||||
get => _moduleSettings.Properties.DoNotActivateOnGameMode.Value;
|
||||
|
||||
set
|
||||
{
|
||||
if (_moduleSettings.Properties.DoNotActivateOnGameMode.Value != value)
|
||||
{
|
||||
_moduleSettings.Properties.DoNotActivateOnGameMode.Value = value;
|
||||
NotifyModuleSettingsChanged();
|
||||
OnPropertyChanged(nameof(DoNotActivateOnGameMode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string ExcludedApps
|
||||
{
|
||||
get => _moduleSettings.Properties.ExcludedApps.Value;
|
||||
|
||||
set
|
||||
{
|
||||
if (_moduleSettings.Properties.ExcludedApps.Value != value)
|
||||
{
|
||||
_moduleSettings.Properties.ExcludedApps.Value = value;
|
||||
NotifyModuleSettingsChanged();
|
||||
OnPropertyChanged(nameof(ExcludedApps));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void NotifyModuleSettingsChanged()
|
||||
{
|
||||
SndGrabAndMoveSettings outSettings = new(_moduleSettings);
|
||||
SndModuleSettings<SndGrabAndMoveSettings> outIpcMessage = new(outSettings);
|
||||
SendConfigMSG(outIpcMessage.ToJsonString());
|
||||
}
|
||||
|
||||
public void RefreshEnabledState()
|
||||
{
|
||||
InitializeEnabledValue();
|
||||
OnPropertyChanged(nameof(IsEnabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@ void ReportGPOValues(const std::filesystem::path &tmpDir)
|
||||
report << "getConfiguredFancyZonesEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredFancyZonesEnabledValue()) << std::endl;
|
||||
report << "getConfiguredFileLocksmithEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredFileLocksmithEnabledValue()) << std::endl;
|
||||
report << "getConfiguredLightSwitchEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredLightSwitchEnabledValue()) << std::endl;
|
||||
report << "getConfiguredGrabAndMoveEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredGrabAndMoveEnabledValue()) << std::endl;
|
||||
report << "getConfiguredSvgPreviewEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredSvgPreviewEnabledValue()) << std::endl;
|
||||
report << "getConfiguredMarkdownPreviewEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredMarkdownPreviewEnabledValue()) << std::endl;
|
||||
report << "getConfiguredMonacoPreviewEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredMonacoPreviewEnabledValue()) << std::endl;
|
||||
|
||||
Reference in New Issue
Block a user