Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Baptiste Augrain 2025-03-24 18:01:09 +01:00
commit 60643232ab
No known key found for this signature in database
GPG Key ID: D0F9263E966FE50B
23 changed files with 558 additions and 27 deletions

View File

@ -13,7 +13,21 @@
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "windows-msvc-x64"
},
{
"name": "Mac",
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/node_modules/node-addon-api",
"${default}"
],
"defines": ["_DEBUG", "MACOS"],
"compilerPath": "/usr/bin/clang++",
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "macos-clang-x64"
}
],
"version": 4
}

12
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,12 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/index.js"
}
]
}

16
.vscode/settings.json vendored
View File

@ -67,6 +67,18 @@
"xstring": "cpp",
"xtr1common": "cpp",
"xtree": "cpp",
"xutility": "cpp"
}
"xutility": "cpp",
"__bit_reference": "cpp",
"__locale": "cpp",
"__split_buffer": "cpp",
"__threading_support": "cpp",
"__verbose_abort": "cpp",
"cstdarg": "cpp",
"cwctype": "cpp",
"execution": "cpp",
"string_view": "cpp",
"variant": "cpp",
"bitset": "cpp",
"print": "cpp"
},
}

View File

@ -16,6 +16,9 @@
['OS=="mac"', {
"sources": [
"src/macos/PolicyWatcher.cc",
"src/macos/StringPolicy.cc",
"src/macos/NumberPolicy.cc",
"src/macos/BooleanPolicy.cc"
],
"defines": [
"MACOS",
@ -38,7 +41,8 @@
"sources": [
"src/windows/PolicyWatcher.cc",
"src/windows/StringPolicy.cc",
"src/windows/NumberPolicy.cc"
"src/windows/NumberPolicy.cc",
"src/windows/BooleanPolicy.cc"
],
"defines": [
"WINDOWS"

7
index.d.ts vendored
View File

@ -9,9 +9,10 @@ interface Watcher {
type StringPolicy = { type: "string" };
type NumberPolicy = { type: "number" };
type BooleanPolicy = { type: "boolean" };
export interface Policies {
[policyName: string]: StringPolicy | NumberPolicy;
[policyName: string]: StringPolicy | NumberPolicy | BooleanPolicy;
}
export type PolicyUpdate<T extends Policies> = {
@ -19,9 +20,11 @@ export type PolicyUpdate<T extends Policies> = {
| undefined
| (T[K] extends StringPolicy
? string
: (T[K] extends BooleanPolicy
? boolean
: T[K] extends NumberPolicy
? number
: never);
: never));
};
export function createWatcher<T extends Policies>(

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "@vscodium/policy-watcher",
"version": "1.1.8",
"version": "1.3.0-2503241459",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@vscodium/policy-watcher",
"version": "1.1.8",
"version": "1.3.0-2503241459",
"license": "MIT",
"dependencies": {
"bindings": "^1.5.0",

View File

@ -1,6 +1,6 @@
{
"name": "@vscodium/policy-watcher",
"version": "1.1.10-2503041159",
"version": "1.3.0-2503241459",
"description": "",
"main": "index.js",
"repository": {

View File

@ -10,11 +10,18 @@
using namespace Napi;
enum class PolicyRefreshResult {
Updated,
Unchanged,
Removed,
NotSet
};
class Policy
{
public:
virtual ~Policy() {}
virtual bool refresh() = 0;
virtual PolicyRefreshResult refresh() = 0;
virtual Value getValue(Env env) const = 0;
const std::string name;

View File

@ -14,6 +14,11 @@
#include <windows.h>
#endif
#ifdef MACOS
#define Boolean CFBoolean
#include <CoreServices/CoreServices.h>
#endif
using namespace Napi;
class PolicyWatcher : public AsyncProgressQueueWorker<const Policy *>
@ -24,6 +29,7 @@ public:
void AddStringPolicy(const std::string name);
void AddNumberPolicy(const std::string name);
void AddBooleanPolicy(const std::string name);
void OnExecute(Napi::Env env);
void Execute(const ExecutionProgress &progress);
@ -38,6 +44,13 @@ protected:
#ifdef WINDOWS
HANDLE handles[4];
#endif
#ifdef MACOS
FSEventStreamRef stream;
dispatch_semaphore_t sem;
bool disposed;
#endif
};
#endif

View File

@ -20,6 +20,7 @@ PolicyWatcher::~PolicyWatcher()
void PolicyWatcher::AddStringPolicy(const std::string name) {}
void PolicyWatcher::AddNumberPolicy(const std::string name) {}
void PolicyWatcher::AddBooleanPolicy(const std::string name) {}
void PolicyWatcher::OnExecute(Napi::Env env) {}
void PolicyWatcher::Execute(const ExecutionProgress &progress) {}
void PolicyWatcher::OnProgress(const Policy *const *policies, size_t count) {}

View File

@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
#include "BooleanPolicy.hh"
using namespace Napi;
BooleanPolicy::BooleanPolicy(const std::string name, const std::string &productName)
: PreferencesPolicy(name, productName)
{
}
Value BooleanPolicy::getJSValue(Env env, bool value) const
{
return Napi::Boolean::New(env, value);
}
std::optional<bool> BooleanPolicy::read() const
{
auto pref = CFPreferencesCopyAppValue(key, appID);
if (pref == NULL)
return std::nullopt;
if (CFGetTypeID(pref) != CFBooleanGetTypeID())
{
CFRelease(pref);
return std::nullopt;
}
bool value = (pref == kCFBooleanTrue);
CFRelease(pref);
return value;
}

View File

@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
#ifndef MAC_BOOLEAN_POLICY_H
#define MAC_BOOLEAN_POLICY_H
#include <napi.h>
#include "PreferencesPolicy.hh"
using namespace Napi;
class BooleanPolicy : public PreferencesPolicy<bool>
{
public:
BooleanPolicy(const std::string name, const std::string &productName);
std::optional<bool> read() const override;
protected:
Value getJSValue(Env env, bool value) const override;
};
#endif

42
src/macos/NumberPolicy.cc Normal file
View File

@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
#include "NumberPolicy.hh"
using namespace Napi;
NumberPolicy::NumberPolicy(const std::string name, const std::string &productName)
: PreferencesPolicy(name, productName)
{
}
Value NumberPolicy::getJSValue(Env env, long long value) const
{
return Number::New(env, static_cast<double>(value));
}
std::optional<long long> NumberPolicy::read() const
{
auto pref = CFPreferencesCopyAppValue(key, appID);
if (pref == NULL)
return std::nullopt;
if (CFGetTypeID(pref) != CFNumberGetTypeID())
{
CFRelease(pref);
return std::nullopt;
}
long long value;
if (CFNumberGetValue((CFNumberRef)pref, kCFNumberLongLongType, &value))
{
CFRelease(pref);
return value;
}
CFRelease(pref);
return std::nullopt;
}

24
src/macos/NumberPolicy.hh Normal file
View File

@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
#ifndef MAC_NUMBER_POLICY_H
#define MAC_NUMBER_POLICY_H
#include <napi.h>
#include "PreferencesPolicy.hh"
using namespace Napi;
class NumberPolicy : public PreferencesPolicy<long long>
{
public:
NumberPolicy(const std::string name, const std::string &productName);
std::optional<long long> read() const override;
protected:
Value getJSValue(Env env, long long value) const override;
};
#endif

View File

@ -4,23 +4,136 @@
*--------------------------------------------------------------------------------------------*/
#include "../PolicyWatcher.hh"
#include "StringPolicy.hh"
#include "NumberPolicy.hh"
#include "BooleanPolicy.hh"
#include <thread>
using namespace Napi;
static void fsevents_callback(ConstFSEventStreamRef streamRef,
void *clientCallBackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[])
{
dispatch_semaphore_t *sem = (dispatch_semaphore_t *)clientCallBackInfo;
dispatch_semaphore_signal(*sem);
}
PolicyWatcher::PolicyWatcher(std::string vendorName, std::string productName, const Function &okCallback)
: AsyncProgressQueueWorker(okCallback),
vendorName(vendorName),
productName(productName)
productName(productName),
stream(nullptr),
sem(nullptr),
disposed(false)
{
}
PolicyWatcher::~PolicyWatcher()
{
if (stream) {
FSEventStreamStop(stream);
FSEventStreamInvalidate(stream);
FSEventStreamRelease(stream);
}
if (sem) {
dispatch_release(sem);
}
}
void PolicyWatcher::AddStringPolicy(const std::string name) {}
void PolicyWatcher::AddNumberPolicy(const std::string name) {}
void PolicyWatcher::OnExecute(Napi::Env env) {}
void PolicyWatcher::Execute(const ExecutionProgress &progress) {}
void PolicyWatcher::OnProgress(const Policy *const *policies, size_t count) {}
void PolicyWatcher::Dispose() {}
void PolicyWatcher::AddStringPolicy(const std::string name)
{
policies.push_back(std::make_unique<StringPolicy>(name, productName));
}
void PolicyWatcher::AddNumberPolicy(const std::string name)
{
policies.push_back(std::make_unique<NumberPolicy>(name, productName));
}
void PolicyWatcher::AddBooleanPolicy(const std::string name)
{
policies.push_back(std::make_unique<BooleanPolicy>(name, productName));
}
void PolicyWatcher::OnExecute(Napi::Env env)
{
AsyncProgressQueueWorker::OnExecute(env);
}
void PolicyWatcher::Execute(const ExecutionProgress &progress)
{
std::vector<const Policy *> updatedPolicies;
bool first = true;
// Watch for changes
CFStringRef path = CFSTR("/Library/Managed Preferences/");
sem = dispatch_semaphore_create(0);
FSEventStreamContext context = {0, &sem, NULL, NULL, NULL};
stream = FSEventStreamCreate(NULL,
&fsevents_callback,
&context,
CFArrayCreate(NULL, (const void **)&path, 1, NULL),
kFSEventStreamEventIdSinceNow,
1.0,
kCFStreamEventNone);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
FSEventStreamSetDispatchQueue(stream, queue);
FSEventStreamStart(stream);
while (!disposed)
{
updatedPolicies.clear();
bool update = false;
for (auto &policy : policies)
{
switch(policy->refresh())
{
case PolicyRefreshResult::Updated:
updatedPolicies.push_back(policy.get());
update = true;
break;
case PolicyRefreshResult::Unchanged:
updatedPolicies.push_back(policy.get());
break;
case PolicyRefreshResult::Removed:
update = true;
break;
}
}
if (first || update)
progress.Send(&updatedPolicies[0], updatedPolicies.size());
first = false;
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
}
void PolicyWatcher::OnProgress(const Policy *const *policies, size_t count)
{
HandleScope scope(Env());
auto result = Object::New(Env());
if (count == 0)
{
Callback().Call(Receiver().Value(), {result});
return;
}
for (size_t i = 0; i < count; i++)
result.Set(policies[i]->name, policies[i]->getValue(Env()));
Callback().Call(Receiver().Value(), {result});
}
void PolicyWatcher::Dispose()
{
disposed = true;
if (sem) {
dispatch_semaphore_signal(sem);
}
}

View File

@ -0,0 +1,72 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
#ifndef PREFERENCES_POLICY_H
#define PREFERENCES_POLICY_H
#include <napi.h>
#include <CoreFoundation/CoreFoundation.h>
#include "../Policy.hh"
using namespace Napi;
template <typename T>
class PreferencesPolicy : public Policy
{
public:
PreferencesPolicy(const std::string name, const std::string &productName)
: Policy(name),
appID(CFStringCreateWithCString(NULL, productName.c_str(), kCFStringEncodingUTF8)),
key(CFStringCreateWithCString(NULL, name.c_str(), kCFStringEncodingUTF8))
{
}
~PreferencesPolicy()
{
CFRelease(appID);
CFRelease(key);
}
PolicyRefreshResult refresh()
{
auto newValue = read();
// Check for no value or removal
if (!newValue.has_value())
{
if (!value.has_value())
return PolicyRefreshResult::NotSet;
value.reset();
return PolicyRefreshResult::Removed;
}
// Is the value updated?
if (value != newValue)
{
value = newValue;
return PolicyRefreshResult::Updated;
}
return PolicyRefreshResult::Unchanged;
}
Value getValue(Env env) const
{
if (!value.has_value())
return env.Undefined();
return getJSValue(env, *value); // value.value() is only supported after macOS 10.13
}
protected:
std::optional<T> value;
const CFStringRef appID;
const CFStringRef key;
virtual Value getJSValue(Env env, T value) const = 0;
virtual std::optional<T> read() const = 0;
private:
};
#endif

46
src/macos/StringPolicy.cc Normal file
View File

@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
#include "StringPolicy.hh"
using namespace Napi;
StringPolicy::StringPolicy(const std::string name, const std::string &productName)
: PreferencesPolicy(name, productName)
{
}
Value StringPolicy::getJSValue(Env env, std::string value) const
{
return String::New(env, value);
}
std::optional<std::string> StringPolicy::read() const
{
auto pref = CFPreferencesCopyAppValue(key, appID);
if (pref == NULL)
return std::nullopt;
if (CFGetTypeID(pref) != CFStringGetTypeID())
{
CFRelease(pref);
return std::nullopt;
}
CFIndex length = CFStringGetLength((CFStringRef)pref);
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
std::vector<char> buffer(maxSize);
if (CFStringGetCString((CFStringRef)pref, buffer.data(), maxSize, kCFStringEncodingUTF8))
{
std::string result(buffer.data());
CFRelease(pref);
return result;
}
CFRelease(pref);
return std::nullopt;
}

23
src/macos/StringPolicy.hh Normal file
View File

@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
#ifndef STRING_POLICY_H
#define STRING_POLICY_H
#include <napi.h>
#include "PreferencesPolicy.hh"
using namespace Napi;
class StringPolicy : public PreferencesPolicy<std::string>
{
public:
StringPolicy(const std::string name, const std::string &productName);
protected:
Value getJSValue(Env env, std::string value) const override;
std::optional<std::string> read() const override;
};
#endif

View File

@ -22,7 +22,7 @@ Value CreateWatcher(const CallbackInfo &info)
{
auto env = info.Env();
#ifndef WINDOWS
#if !defined(WINDOWS) && !defined(MACOS)
throw TypeError::New(env, "Unsupported platform");
#endif
@ -57,18 +57,23 @@ Value CreateWatcher(const CallbackInfo &info)
auto policyType = std::string(rawPolicyType.As<String>());
if (policyType == "string")
if (policyType == "string") {
watcher->AddStringPolicy(rawPolicyName.As<String>());
else if (policyType == "number")
}
else if (policyType == "number") {
watcher->AddNumberPolicy(rawPolicyName.As<String>());
else
} else if (policyType == "boolean") {
watcher->AddBooleanPolicy(rawPolicyName.As<String>());
} else {
throw TypeError::New(env, "Unknown policy type '" + policyType + "'");
}
}
watcher->Queue();
auto result = Object::New(env);
result.Set(String::New(env, "dispose"), Function::New(env, DisposeWatcher, "disposeWatcher", watcher));
return result;
}

View File

@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
#include "BooleanPolicy.hh"
#include <iostream>
using namespace Napi;
BooleanPolicy::BooleanPolicy(const std::string& name, const std::string& productName)
: RegistryPolicy(name, productName, {REG_DWORD}) {}
bool BooleanPolicy::parseRegistryValue(LPBYTE buffer, DWORD bufferSize, DWORD type) const
{
if (type != REG_DWORD || bufferSize != sizeof(DWORD))
{
return false;
}
DWORD value = *reinterpret_cast<DWORD*>(buffer);
return (value != 0);
}
Value BooleanPolicy::getJSValue(Env env, bool value) const
{
return Boolean::New(env, value);
}

View File

@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
#ifndef BOOLEAN_POLICY_H
#define BOOLEAN_POLICY_H
#include <napi.h>
#include <windows.h>
#include "RegistryPolicy.hh"
using namespace Napi;
class BooleanPolicy : public RegistryPolicy<bool>
{
public:
BooleanPolicy(const std::string& name, const std::string &productName);
protected:
bool parseRegistryValue(LPBYTE buffer, DWORD bufferSize, DWORD type) const;
Value getJSValue(Env env, bool value) const;
};
#endif

View File

@ -8,6 +8,7 @@
#include "../PolicyWatcher.hh"
#include "StringPolicy.hh"
#include "NumberPolicy.hh"
#include "BooleanPolicy.hh"
using namespace Napi;
@ -38,6 +39,11 @@ void PolicyWatcher::AddNumberPolicy(const std::string name)
policies.push_back(std::make_unique<NumberPolicy>(name, vendorName, productName));
}
void PolicyWatcher::AddBooleanPolicy(const std::string name)
{
policies.push_back(std::make_unique<BooleanPolicy>(name, productName));
}
void PolicyWatcher::OnExecute(Napi::Env env)
{
if ((handles[0] = CreateEvent(NULL, false, false, NULL)) == NULL)
@ -64,15 +70,26 @@ void PolicyWatcher::Execute(const ExecutionProgress &progress)
{
std::vector<const Policy *> updatedPolicies;
bool update = false;
updatedPolicies.clear();
for (auto &policy : policies)
{
if (policy->refresh())
switch (policy->refresh())
{
case PolicyRefreshResult::Updated:
updatedPolicies.push_back(policy.get());
update = true;
break;
case PolicyRefreshResult::Unchanged:
updatedPolicies.push_back(policy.get());
break;
case PolicyRefreshResult::Removed:
update = true;
break;
}
}
if (first || updatedPolicies.size() > 0)
if (first || update)
progress.Send(&updatedPolicies[0], updatedPolicies.size());
first = false;

View File

@ -24,7 +24,7 @@ public:
registryKey("Software\\Policies\\" + vendorName + "\\" + productName),
supportedTypes(types) {}
bool refresh()
PolicyRefreshResult refresh()
{
auto machine = read(HKEY_LOCAL_MACHINE);
@ -33,21 +33,31 @@ public:
if (value != machine)
{
value = machine;
return true;
return PolicyRefreshResult::Updated;
}
return false;
return PolicyRefreshResult::Unchanged;
}
auto user = read(HKEY_CURRENT_USER);
// Check for no value or removal
if (!user.has_value())
{
if (!value.has_value())
return PolicyRefreshResult::NotSet;
value.reset();
return PolicyRefreshResult::Removed;
}
if (value != user)
{
value = user;
return true;
return PolicyRefreshResult::Updated;
}
return false;
return PolicyRefreshResult::Unchanged;
}
Value getValue(Env env) const