mirror of
https://github.com/obsproject/obs-studio.git
synced 2026-03-18 14:37:13 -04:00
While the NSURLVolumeAvailableCapacityForOpportunisticUsageKey resource correctly reports available disk space for local volumes (regardless of actual file system used), it does not report actual values for attached network drives. The NSURLVolumeAvailableCapacityKey resource will still report available disk space as expected, so use this value for non-local volumes instead.
535 lines
14 KiB
Objective-C
535 lines
14 KiB
Objective-C
/*
|
|
* Copyright (c) 2023 Ruwen Hahn <palana@stunned.de>
|
|
* Lain Bailey <lain@obsproject.com>
|
|
* Marvin Scholz <epirat07@gmail.com>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include "base.h"
|
|
#include "platform.h"
|
|
#include "dstr.h"
|
|
|
|
#include <dlfcn.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/sysctl.h>
|
|
|
|
#include <CoreServices/CoreServices.h>
|
|
#include <mach/mach.h>
|
|
#include <mach/mach_time.h>
|
|
#include <mach-o/dyld.h>
|
|
|
|
#include <IOKit/pwr_mgt/IOPMLib.h>
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
|
|
#include "apple/cfstring-utils.h"
|
|
|
|
uint64_t os_gettime_ns(void)
|
|
{
|
|
return clock_gettime_nsec_np(CLOCK_UPTIME_RAW);
|
|
}
|
|
|
|
/* gets the location [domain mask]/Library/Application Support/[name] */
|
|
static int os_get_path_internal(char *dst, size_t size, const char *name, NSSearchPathDomainMask domainMask)
|
|
{
|
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, domainMask, YES);
|
|
|
|
if ([paths count] == 0)
|
|
bcrash("Could not get home directory (platform-cocoa)");
|
|
|
|
NSString *application_support = paths[0];
|
|
const char *base_path = [application_support UTF8String];
|
|
|
|
if (!name || !*name)
|
|
return snprintf(dst, size, "%s", base_path);
|
|
else
|
|
return snprintf(dst, size, "%s/%s", base_path, name);
|
|
}
|
|
|
|
static char *os_get_path_ptr_internal(const char *name, NSSearchPathDomainMask domainMask)
|
|
{
|
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, domainMask, YES);
|
|
|
|
if ([paths count] == 0)
|
|
bcrash("Could not get home directory (platform-cocoa)");
|
|
|
|
NSString *application_support = paths[0];
|
|
|
|
NSUInteger len = [application_support lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
char *path_ptr = bmalloc(len + 1);
|
|
|
|
path_ptr[len] = 0;
|
|
|
|
memcpy(path_ptr, [application_support UTF8String], len);
|
|
|
|
struct dstr path;
|
|
dstr_init_move_array(&path, path_ptr);
|
|
dstr_cat(&path, "/");
|
|
dstr_cat(&path, name);
|
|
return path.array;
|
|
}
|
|
|
|
int os_get_config_path(char *dst, size_t size, const char *name)
|
|
{
|
|
return os_get_path_internal(dst, size, name, NSUserDomainMask);
|
|
}
|
|
|
|
char *os_get_config_path_ptr(const char *name)
|
|
{
|
|
return os_get_path_ptr_internal(name, NSUserDomainMask);
|
|
}
|
|
|
|
int os_get_program_data_path(char *dst, size_t size, const char *name)
|
|
{
|
|
return os_get_path_internal(dst, size, name, NSLocalDomainMask);
|
|
}
|
|
|
|
char *os_get_program_data_path_ptr(const char *name)
|
|
{
|
|
return os_get_path_ptr_internal(name, NSLocalDomainMask);
|
|
}
|
|
|
|
char *os_get_executable_path_ptr(const char *name)
|
|
{
|
|
char exe[PATH_MAX];
|
|
char abs_path[PATH_MAX];
|
|
uint32_t size = sizeof(exe);
|
|
struct dstr path;
|
|
char *slash;
|
|
|
|
if (_NSGetExecutablePath(exe, &size) != 0) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!realpath(exe, abs_path)) {
|
|
return NULL;
|
|
}
|
|
|
|
dstr_init_copy(&path, abs_path);
|
|
slash = strrchr(path.array, '/');
|
|
if (slash) {
|
|
size_t len = slash - path.array + 1;
|
|
dstr_resize(&path, len);
|
|
}
|
|
|
|
if (name && *name) {
|
|
dstr_cat(&path, name);
|
|
}
|
|
return path.array;
|
|
}
|
|
|
|
struct os_cpu_usage_info {
|
|
int64_t last_cpu_time;
|
|
int64_t last_sys_time;
|
|
int core_count;
|
|
};
|
|
|
|
static inline void add_time_value(time_value_t *dst, time_value_t *a, time_value_t *b)
|
|
{
|
|
dst->microseconds = a->microseconds + b->microseconds;
|
|
dst->seconds = a->seconds + b->seconds;
|
|
|
|
if (dst->microseconds >= 1000000) {
|
|
dst->seconds += dst->microseconds / 1000000;
|
|
dst->microseconds %= 1000000;
|
|
}
|
|
}
|
|
|
|
static bool get_time_info(int64_t *cpu_time, int64_t *sys_time)
|
|
{
|
|
mach_port_t task = mach_task_self();
|
|
struct task_thread_times_info thread_data;
|
|
struct task_basic_info_64 task_data;
|
|
mach_msg_type_number_t count;
|
|
kern_return_t kern_ret;
|
|
time_value_t cur_time;
|
|
|
|
*cpu_time = 0;
|
|
*sys_time = 0;
|
|
|
|
count = TASK_THREAD_TIMES_INFO_COUNT;
|
|
kern_ret = task_info(task, TASK_THREAD_TIMES_INFO, (task_info_t) &thread_data, &count);
|
|
if (kern_ret != KERN_SUCCESS)
|
|
return false;
|
|
|
|
count = TASK_BASIC_INFO_64_COUNT;
|
|
kern_ret = task_info(task, TASK_BASIC_INFO_64, (task_info_t) &task_data, &count);
|
|
if (kern_ret != KERN_SUCCESS)
|
|
return false;
|
|
|
|
add_time_value(&cur_time, &thread_data.user_time, &thread_data.system_time);
|
|
add_time_value(&cur_time, &cur_time, &task_data.user_time);
|
|
add_time_value(&cur_time, &cur_time, &task_data.system_time);
|
|
|
|
*cpu_time = os_gettime_ns() / 1000;
|
|
*sys_time = cur_time.seconds * 1000000 + cur_time.microseconds;
|
|
return true;
|
|
}
|
|
|
|
os_cpu_usage_info_t *os_cpu_usage_info_start(void)
|
|
{
|
|
struct os_cpu_usage_info *info = bmalloc(sizeof(*info));
|
|
|
|
if (!get_time_info(&info->last_cpu_time, &info->last_sys_time)) {
|
|
bfree(info);
|
|
return NULL;
|
|
}
|
|
|
|
info->core_count = (int) sysconf(_SC_NPROCESSORS_ONLN);
|
|
return info;
|
|
}
|
|
|
|
double os_cpu_usage_info_query(os_cpu_usage_info_t *info)
|
|
{
|
|
int64_t sys_time, cpu_time;
|
|
int64_t sys_time_delta, cpu_time_delta;
|
|
|
|
if (!info || !get_time_info(&cpu_time, &sys_time))
|
|
return 0.0;
|
|
|
|
sys_time_delta = sys_time - info->last_sys_time;
|
|
cpu_time_delta = cpu_time - info->last_cpu_time;
|
|
|
|
if (cpu_time_delta == 0)
|
|
return 0.0;
|
|
|
|
info->last_sys_time = sys_time;
|
|
info->last_cpu_time = cpu_time;
|
|
|
|
return (double) sys_time_delta * 100.0 / (double) cpu_time_delta / (double) info->core_count;
|
|
}
|
|
|
|
void os_cpu_usage_info_destroy(os_cpu_usage_info_t *info)
|
|
{
|
|
if (info)
|
|
bfree(info);
|
|
}
|
|
|
|
os_performance_token_t *os_request_high_performance(const char *reason)
|
|
{
|
|
@autoreleasepool {
|
|
NSProcessInfo *processInfo = NSProcessInfo.processInfo;
|
|
id activity = [processInfo beginActivityWithOptions:NSActivityUserInitiated reason:@(reason ? reason : "")];
|
|
|
|
return CFBridgingRetain(activity);
|
|
}
|
|
}
|
|
|
|
void os_end_high_performance(os_performance_token_t *token)
|
|
{
|
|
@autoreleasepool {
|
|
NSProcessInfo *processInfo = NSProcessInfo.processInfo;
|
|
[processInfo endActivity:CFBridgingRelease(token)];
|
|
}
|
|
}
|
|
|
|
struct os_inhibit_info {
|
|
CFStringRef reason;
|
|
IOPMAssertionID sleep_id;
|
|
IOPMAssertionID user_id;
|
|
bool active;
|
|
};
|
|
|
|
os_inhibit_t *os_inhibit_sleep_create(const char *reason)
|
|
{
|
|
struct os_inhibit_info *info = bzalloc(sizeof(*info));
|
|
if (!reason)
|
|
info->reason = CFStringCreateWithCString(kCFAllocatorDefault, reason, kCFStringEncodingUTF8);
|
|
else
|
|
info->reason = CFStringCreateCopy(kCFAllocatorDefault, CFSTR(""));
|
|
|
|
return info;
|
|
}
|
|
|
|
bool os_inhibit_sleep_set_active(os_inhibit_t *info, bool active)
|
|
{
|
|
IOReturn success;
|
|
|
|
if (!info)
|
|
return false;
|
|
if (info->active == active)
|
|
return false;
|
|
|
|
if (active) {
|
|
IOPMAssertionDeclareUserActivity(info->reason, kIOPMUserActiveLocal, &info->user_id);
|
|
success = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, info->reason,
|
|
&info->sleep_id);
|
|
|
|
if (success != kIOReturnSuccess) {
|
|
blog(LOG_WARNING, "Failed to disable sleep");
|
|
return false;
|
|
}
|
|
} else {
|
|
IOPMAssertionRelease(info->sleep_id);
|
|
}
|
|
|
|
info->active = active;
|
|
return true;
|
|
}
|
|
|
|
void os_inhibit_sleep_destroy(os_inhibit_t *info)
|
|
{
|
|
if (info) {
|
|
os_inhibit_sleep_set_active(info, false);
|
|
CFRelease(info->reason);
|
|
bfree(info);
|
|
}
|
|
}
|
|
|
|
static int physical_cores = 0;
|
|
static int logical_cores = 0;
|
|
static bool core_count_initialized = false;
|
|
|
|
bool os_get_emulation_status(void)
|
|
{
|
|
#ifdef __aarch64__
|
|
return false;
|
|
#else
|
|
int rosettaTranslated = 0;
|
|
size_t size = sizeof(rosettaTranslated);
|
|
if (sysctlbyname("sysctl.proc_translated", &rosettaTranslated, &size, NULL, 0) == -1)
|
|
return false;
|
|
|
|
return rosettaTranslated == 1;
|
|
#endif
|
|
}
|
|
|
|
static void os_get_cores_internal(void)
|
|
{
|
|
if (core_count_initialized)
|
|
return;
|
|
|
|
core_count_initialized = true;
|
|
|
|
size_t size;
|
|
int ret;
|
|
|
|
size = sizeof(physical_cores);
|
|
ret = sysctlbyname("machdep.cpu.core_count", &physical_cores, &size, NULL, 0);
|
|
if (ret != 0)
|
|
return;
|
|
|
|
ret = sysctlbyname("machdep.cpu.thread_count", &logical_cores, &size, NULL, 0);
|
|
}
|
|
|
|
int os_get_physical_cores(void)
|
|
{
|
|
if (!core_count_initialized)
|
|
os_get_cores_internal();
|
|
return physical_cores;
|
|
}
|
|
|
|
int os_get_logical_cores(void)
|
|
{
|
|
if (!core_count_initialized)
|
|
os_get_cores_internal();
|
|
return logical_cores;
|
|
}
|
|
|
|
static inline bool os_get_sys_memory_usage_internal(vm_statistics_t vmstat)
|
|
{
|
|
mach_msg_type_number_t out_count = HOST_VM_INFO_COUNT;
|
|
if (host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t) vmstat, &out_count) != KERN_SUCCESS)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
uint64_t os_get_sys_free_size(void)
|
|
{
|
|
vm_statistics_data_t vmstat = {};
|
|
if (!os_get_sys_memory_usage_internal(&vmstat))
|
|
return 0;
|
|
|
|
return vmstat.free_count * vm_page_size;
|
|
}
|
|
|
|
int64_t os_get_free_space(const char *path)
|
|
{
|
|
if (path) {
|
|
NSURL *fileURL = [NSURL fileURLWithPath:@(path)];
|
|
|
|
NSDictionary *values = [fileURL resourceValuesForKeys:@[NSURLVolumeIsLocalKey] error:nil];
|
|
|
|
BOOL isLocalVolume = [values[NSURLVolumeIsLocalKey] boolValue];
|
|
|
|
NSURLResourceKey volumeKey;
|
|
|
|
if (isLocalVolume) {
|
|
volumeKey = NSURLVolumeAvailableCapacityForOpportunisticUsageKey;
|
|
} else {
|
|
volumeKey = NSURLVolumeAvailableCapacityKey;
|
|
}
|
|
|
|
values = [fileURL resourceValuesForKeys:@[volumeKey] error:nil];
|
|
|
|
NSNumber *availableSpace = values[volumeKey];
|
|
|
|
if (availableSpace) {
|
|
return availableSpace.longValue;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint64_t os_get_free_disk_space(const char *dir)
|
|
{
|
|
int64_t free_space = os_get_free_space(dir);
|
|
|
|
return (uint64_t) free_space;
|
|
}
|
|
|
|
static uint64_t total_memory = 0;
|
|
static bool total_memory_initialized = false;
|
|
|
|
static void os_get_sys_total_size_internal()
|
|
{
|
|
total_memory_initialized = true;
|
|
|
|
size_t size;
|
|
int ret;
|
|
|
|
size = sizeof(total_memory);
|
|
ret = sysctlbyname("hw.memsize", &total_memory, &size, NULL, 0);
|
|
}
|
|
|
|
uint64_t os_get_sys_total_size(void)
|
|
{
|
|
if (!total_memory_initialized)
|
|
os_get_sys_total_size_internal();
|
|
|
|
return total_memory;
|
|
}
|
|
|
|
static inline bool os_get_proc_memory_usage_internal(mach_task_basic_info_data_t *taskinfo)
|
|
{
|
|
const task_flavor_t flavor = MACH_TASK_BASIC_INFO;
|
|
mach_msg_type_number_t out_count = MACH_TASK_BASIC_INFO_COUNT;
|
|
|
|
if (task_info(mach_task_self(), flavor, (task_info_t) taskinfo, &out_count) != KERN_SUCCESS)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool os_get_proc_memory_usage(os_proc_memory_usage_t *usage)
|
|
{
|
|
mach_task_basic_info_data_t taskinfo = {};
|
|
if (!os_get_proc_memory_usage_internal(&taskinfo))
|
|
return false;
|
|
|
|
usage->resident_size = taskinfo.resident_size;
|
|
usage->virtual_size = taskinfo.virtual_size;
|
|
return true;
|
|
}
|
|
|
|
uint64_t os_get_proc_resident_size(void)
|
|
{
|
|
mach_task_basic_info_data_t taskinfo = {};
|
|
if (!os_get_proc_memory_usage_internal(&taskinfo))
|
|
return 0;
|
|
return taskinfo.resident_size;
|
|
}
|
|
|
|
uint64_t os_get_proc_virtual_size(void)
|
|
{
|
|
mach_task_basic_info_data_t taskinfo = {};
|
|
if (!os_get_proc_memory_usage_internal(&taskinfo))
|
|
return 0;
|
|
return taskinfo.virtual_size;
|
|
}
|
|
|
|
/* Obtains a copy of the contents of a CFString in specified encoding.
|
|
* Returns char* (must be bfree'd by caller) or NULL on failure.
|
|
*/
|
|
char *cfstr_copy_cstr(CFStringRef cfstring, CFStringEncoding cfstring_encoding)
|
|
{
|
|
if (!cfstring)
|
|
return NULL;
|
|
|
|
// Try the quick way to obtain the buffer
|
|
const char *tmp_buffer = CFStringGetCStringPtr(cfstring, cfstring_encoding);
|
|
|
|
if (tmp_buffer != NULL)
|
|
return bstrdup(tmp_buffer);
|
|
|
|
// The quick way did not work, try the more expensive one
|
|
CFIndex length = CFStringGetLength(cfstring);
|
|
CFIndex max_size = CFStringGetMaximumSizeForEncoding(length, cfstring_encoding);
|
|
|
|
// If result would exceed LONG_MAX, kCFNotFound is returned
|
|
if (max_size == kCFNotFound)
|
|
return NULL;
|
|
|
|
// Account for the null terminator
|
|
max_size++;
|
|
|
|
char *buffer = bmalloc(max_size);
|
|
|
|
if (buffer == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
// Copy CFString in requested encoding to buffer
|
|
Boolean success = CFStringGetCString(cfstring, buffer, max_size, cfstring_encoding);
|
|
|
|
if (!success) {
|
|
bfree(buffer);
|
|
buffer = NULL;
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
/* Copies the contents of a CFString in specified encoding to a given dstr.
|
|
* Returns true on success or false on failure.
|
|
* In case of failure, the dstr capacity but not size is changed.
|
|
*/
|
|
bool cfstr_copy_dstr(CFStringRef cfstring, CFStringEncoding cfstring_encoding, struct dstr *str)
|
|
{
|
|
if (!cfstring)
|
|
return false;
|
|
|
|
// Try the quick way to obtain the buffer
|
|
const char *tmp_buffer = CFStringGetCStringPtr(cfstring, cfstring_encoding);
|
|
|
|
if (tmp_buffer != NULL) {
|
|
dstr_copy(str, tmp_buffer);
|
|
return true;
|
|
}
|
|
|
|
// The quick way did not work, try the more expensive one
|
|
CFIndex length = CFStringGetLength(cfstring);
|
|
CFIndex max_size = CFStringGetMaximumSizeForEncoding(length, cfstring_encoding);
|
|
|
|
// If result would exceed LONG_MAX, kCFNotFound is returned
|
|
if (max_size == kCFNotFound)
|
|
return NULL;
|
|
|
|
// Account for the null terminator
|
|
max_size++;
|
|
|
|
dstr_ensure_capacity(str, max_size);
|
|
|
|
// Copy CFString in requested encoding to dstr buffer
|
|
Boolean success = CFStringGetCString(cfstring, str->array, max_size, cfstring_encoding);
|
|
|
|
if (success)
|
|
dstr_resize(str, max_size);
|
|
|
|
return (bool) success;
|
|
}
|