Compare commits

..

No commits in common. "main" and "v1.0.5" have entirely different histories.
main ... v1.0.5

12 changed files with 72 additions and 275 deletions

145
README.md
View File

@ -1,145 +0,0 @@
# falcond - Advanced Linux Gaming Performance Daemon
falcond is a powerful system daemon designed to automatically optimize your Linux gaming experience. It intelligently manages system resources and performance settings on a per-game basis, eliminating the need to manually configure settings for each game.
## Features
- **Automatic Game Detection**: Automatically detects running games and applies optimized settings
- **Per-Game Profiles**: Customizable profiles for different games
- **Performance Mode**: Enables system-wide performance optimizations when gaming
- **3D vcache Management**: Smart management of AMD 3D vcache settings
- **SCX Scheduler Integration**: Dynamically pick a scheduler that is best for the specific game
- **Proton Compatibility**: Full support for Steam Proton games, with a global profile for excellent game coverage
- **Low Overhead**: Minimal system resource usage
- **Different Device Modes**: Profiles for desktops, handhelds, HTPC etc
## Installation
falcond is available on PikaOS via the falcond package or via the Pika Gameutils Metapackage. For other distributions, please check your package manager or build from source.
## Configuration
The configuration file is located at `/etc/falcond/config.conf`. Here's an example configuration, it will be generated automatically on first run:
```
enable_performance_mode = true
scx_sched = none
scx_sched_props = default
vcache_mode = none
profile_mode = none
```
This is global configuration and all options other than profile_mode override individual game profiles.
There is also a list of proton/wine system processes in `/usr/share/falcond/system.conf`. This list can be updated if for example a crash handler is intercepting your profile and needs to be ignored.
### Configuration Options
- `enable_performance_mode`: Enable/disable performance mode (default: true)
- `scx_sched`: SCX scheduler (options: none, bpfland, lavd, rusty, flash)
- `scx_sched_props`: SCX scheduler mode (options: default, gaming, power, latency, server)
- `vcache_mode`: VCache management mode (options: none, cache, freq)
- `profile_mode`: What type of device is in use, none means desktop (options: none, handheld, htpc)
## Profile Modes
Falcond now supports different profile modes for different device types:
- Default (none): Uses profiles from `/usr/share/falcond/profiles`
- Handheld: Uses profiles from `/usr/share/falcond/profiles/handheld`
- HTPC: Uses profiles from `/usr/share/falcond/profiles/htpc`
To set a profile mode, add this to your config file:
```
profile_mode = "handheld" # or "htpc" or "none"
```
## Game Profiles
Game profiles are stored in `/usr/share/falcond/profiles/` and define specific optimizations for individual games. You can contribute new profiles via PR at:
[Falcond Profiles Repository](https://github.com/PikaOS-Linux/falcond-profiles)
Example profile:
```
name = "game.exe"
performance_mode = true
scx_sched = bpfland
scx_sched_props = gaming
vcache_mode = cache
```
### Available Options
- `name`: The exe name (examples: cs2, PathOfExileSteam.exe)
- `performance_mode`: Enable/disable performance mode (default: true)
- `scx_sched`: SCX scheduler (options: none, bpfland, lavd, rusty, flash)
- `scx_sched_props`: SCX scheduler mode (options: none, gaming, power, latency, server)
- `vcache_mode`: VCache management mode (options: none, cache, freq)
## Service Management
To restart the daemon:
```bash
sudo systemctl restart falcond
```
To check the status:
```
sudo systemctl status falcond
```
## Source Code
The source code for falcond can be found at:
[Falcond Source Repository](https://git.pika-os.com/general-packages/falcond)
## Important Notes
⚠️ **Compatibility Warning** ⚠️ falcond should not be used alongside Feral GameMode or Falcon GameMode as they may conflict with each other. falcond provides similar functionality with additional features and optimizations.
## Why falcond?
Traditional gaming on Linux often requires manual optimization for each game - tweaking CPU governors, scheduling priorities, and cache settings. falcond automates this entire process by:
1. Automatically detecting when games are running
2. Applying optimized settings based on pre-configured profiles
3. Reverting settings when games are closed
4. Providing a centralized way to manage gaming performance
5. Easy switching of options for different device types
This means you can focus on gaming while falcond handles all the technical optimizations in the background!
## License
falcond is released under the [MIT License](http://git.pika-os.com/general-packages/falcond/raw/branch/main/LICENSE).
## Contributing
Please fork the [PikaOS-Linux/falcond](https://github.com/PikaOS-Linux/falcond) repository and submit a pull request.
## Build Dependencies
zig 0.14.0+
## Building from Source
```
git clone https://git.pika-os.com/general-packages/falcond.git
cd falcond
cd falcond
zig build -Doptimize=ReleaseFast
```
## Runtime Dependencies
These should be feature detected by falcond so if not present that specific feature will not be used.
power-profiles-daemon or tuned + tuned-ppd
scx-sched
Linux kernel patched with AMD 3D vcache support
## Packaging
falcond should be placed in /usr/bin/falcond and run via a service file. There is an example systemd service file in ./falcond/debian/falcond.service
falcond needs profiles to be useful, these should be placed in /usr/share/falcond/profiles alongside the system.conf in /usr/share/falcond/system.conf. Upto date profiles can be found in the [PikaOS-Linux/falcond-profiles](https://github.com/PikaOS-Linux/falcond-profiles) repository. We currently pull the latest profiles from there on building of this package but you could also package seperately and depend on that package.
There is a config file in /etc/falcond/config.conf which is generated automatically on first run. You could also package that if you need different default settings.

View File

@ -1,21 +1,3 @@
falcond (1.0.8-101pika2) pika; urgency=low
* Fix scx scheduler mode selection order
-- ferreo <ferreo@pika-os.com> Sun, 12 Jan 2025 13:48:00 +0300
falcond (1.0.7-101pika2) pika; urgency=low
* Don't load profiles in subfolders
-- ferreo <ferreo@pika-os.com> Sun, 12 Jan 2025 13:48:00 +0300
falcond (1.0.6-101pika1) pika; urgency=low
* Add handheld and htpc profiles, split system processes config to live in the profiles repo for easier updates
-- ferreo <ferreo@pika-os.com> Sun, 12 Jan 2025 13:48:00 +0300
falcond (1.0.5-101pika1) pika; urgency=low
* Fix scx scheduler reactivation + improve info logging - actual fix it this time

View File

@ -24,7 +24,7 @@ override_dh_install:
chmod 755 debian/falcond/usr/bin/falcond
chmod +x debian/falcond/usr/bin/falcond
# Copy profiles
cp -r falcond-profiles/usr/share/falcond debian/falcond/usr/share/
cp -r falcond-profiles/usr/share/falcond/profiles debian/falcond/usr/share/falcond/
override_dh_installsystemd:
dh_installsystemd --name=falcond --restart-after-upgrade

View File

@ -75,11 +75,7 @@ pub fn init(alloc: std.mem.Allocator) !void {
allocator = alloc;
std.log.info("Initializing scheduler state", .{});
const sched_list = getSupportedSchedulers(alloc) catch |err| {
std.log.warn("Failed to get supported schedulers: {}, scx_loader may not be available", .{err});
supported_schedulers = &[_]ScxScheduler{};
return;
};
const sched_list = try getSupportedSchedulers(alloc);
defer alloc.free(sched_list);
std.log.info("Supported schedulers:", .{});
@ -106,8 +102,8 @@ const SCX_IFACE = "org.scx.Loader";
fn modeToInt(mode: ScxSchedModes) u32 {
return switch (mode) {
.default => 0,
.gaming => 1,
.power => 2,
.power => 1,
.gaming => 2,
.latency => 3,
.server => 4,
};
@ -116,8 +112,8 @@ fn modeToInt(mode: ScxSchedModes) u32 {
fn intToMode(value: u32) ScxError!ScxSchedModes {
return switch (value) {
0 => .default,
1 => .gaming,
2 => .power,
1 => .power,
2 => .gaming,
3 => .latency,
4 => .server,
else => error.InvalidValue,
@ -127,38 +123,23 @@ fn intToMode(value: u32) ScxError!ScxSchedModes {
pub fn getCurrentScheduler(alloc: std.mem.Allocator) !?ScxScheduler {
var dbus_conn = dbus.DBus.init(alloc, SCX_NAME, SCX_PATH, SCX_IFACE);
const current = dbus_conn.getProperty("CurrentScheduler") catch |err| {
std.log.warn("Failed to get current scheduler: {}", .{err});
return .none;
};
const current = try dbus_conn.getProperty("CurrentScheduler");
defer alloc.free(current);
if (current.len == 0) return .none;
return ScxScheduler.fromString(current) catch |err| {
std.log.warn("Invalid scheduler value: {}", .{err});
return .none;
};
if (current.len == 0) return null;
return try ScxScheduler.fromString(current);
}
pub fn getCurrentMode(alloc: std.mem.Allocator) !ScxSchedModes {
var dbus_conn = dbus.DBus.init(alloc, SCX_NAME, SCX_PATH, SCX_IFACE);
const mode_str = dbus_conn.getProperty("SchedulerMode") catch |err| {
std.log.warn("Failed to get scheduler mode: {}", .{err});
return .default;
};
const mode_str = try dbus_conn.getProperty("SchedulerMode");
defer alloc.free(mode_str);
if (mode_str.len == 0) return .default;
const mode = std.fmt.parseInt(u32, mode_str, 10) catch |err| {
std.log.warn("Invalid scheduler mode value: {}", .{err});
return .default;
};
return intToMode(mode) catch |err| {
std.log.warn("Invalid mode value: {}", .{err});
return .default;
};
const mode = try std.fmt.parseInt(u32, mode_str, 10);
return intToMode(mode);
}
pub fn getSupportedSchedulers(alloc: std.mem.Allocator) ![]ScxScheduler {
@ -266,21 +247,23 @@ fn runSystemCtl(alloc: std.mem.Allocator, command: []const u8, service: []const
.allocator = alloc,
.argv = &argv,
.max_output_bytes = max_output_size,
}) catch {
}) catch |err| {
std.log.warn("Failed to run systemctl {s} {s}: {}", .{ command, service, err });
return;
};
defer alloc.free(result.stderr);
defer alloc.free(result.stdout);
if (result.term.Exited != 0) {
std.log.warn("systemctl failed: {s}", .{result.stderr});
return;
}
}
pub fn deactivateScheduler(alloc: std.mem.Allocator) ScxError!void {
runSystemCtl(alloc, "start", "ananicy-cpp");
var dbus_conn = dbus.DBus.init(alloc, SCX_NAME, SCX_PATH, SCX_IFACE);
try dbus_conn.callMethod("StopScheduler", &[_][]const u8{});
runSystemCtl(alloc, "start", "ananicy-cpp");
}
pub fn restorePreviousState(alloc: std.mem.Allocator) void {

View File

@ -4,14 +4,32 @@ const confloader = @import("confloader.zig");
const vcache_setting = @import("../clients/vcache_setting.zig");
const scx_scheds = @import("../clients/scx_scheds.zig");
pub const ProfileMode = enum {
none,
handheld,
htpc,
};
pub const SystemConfig = struct {
system_processes: []const []const u8 = &[_][]const u8{},
const default_system_processes = [_][]const u8{
"steam.exe",
"services.exe",
"winedevice.exe",
"plugplay.exe",
"svchost.exe",
"explorer.exe",
"rpcss.exe",
"tabtip.exe",
"wineboot.exe",
"rundll32.exe",
"iexplore.exe",
"conhost.exe",
"crashpad_handler.exe",
"iscriptevaluator.exe",
"VC_redist.x86.exe",
"VC_redist.x64.exe",
"cmd.exe",
"REDEngineErrorReporter.exe",
"REDprelauncher.exe",
"SteamService.exe",
"UnityCrashHandler64.exe",
"start.exe",
"CrashReportClient.exe",
"Battle.net.exe",
"Agent.exe",
};
pub const Config = struct {
@ -19,11 +37,10 @@ pub const Config = struct {
scx_sched: scx_scheds.ScxScheduler = .none,
scx_sched_props: ?scx_scheds.ScxSchedModes = null,
vcache_mode: vcache_setting.VCacheMode = .none,
system_processes: []const []const u8 = &[_][]const u8{},
profile_mode: ProfileMode = .none,
system_processes: []const []const u8 = &default_system_processes,
arena: ?std.heap.ArenaAllocator = null,
pub fn load(allocator: std.mem.Allocator, path: []const u8, system_conf_path: ?[]const u8) !Config {
pub fn load(allocator: std.mem.Allocator, path: []const u8) !Config {
var config = Config{};
const file = fs.openFileAbsolute(path, .{}) catch |err| switch (err) {
error.FileNotFound => {
@ -38,18 +55,6 @@ pub const Config = struct {
errdefer arena.deinit();
config = try confloader.loadConf(Config, arena.allocator(), path);
if (system_conf_path) |sys_path| {
const system_file = fs.openFileAbsolute(sys_path, .{}) catch |err| switch (err) {
error.FileNotFound => null,
else => return err,
};
if (system_file) |sf| {
defer sf.close();
const system_config = try confloader.loadConf(SystemConfig, arena.allocator(), sys_path);
config.system_processes = system_config.system_processes;
}
}
config.arena = arena;
return config;
}
@ -80,6 +85,11 @@ pub const Config = struct {
try file.writer().print("enable_performance_mode = {}\n", .{self.enable_performance_mode});
try file.writer().print("scx_sched = {s}\n", .{@tagName(self.scx_sched)});
try file.writer().print("vcache_mode = {s}\n", .{@tagName(self.vcache_mode)});
try file.writer().print("profile_mode = {s}\n", .{@tagName(self.profile_mode)});
try file.writer().print("system_processes = [\n", .{});
for (self.system_processes) |proc| {
try file.writer().print(" \"{s}\",\n", .{proc});
}
try file.writer().print("]\n", .{});
}
};

View File

@ -30,10 +30,12 @@ pub fn loadConfDir(comptime T: type, allocator: std.mem.Allocator, dir_path: []c
var dir = try std.fs.openDirAbsolute(dir_path, .{ .iterate = true });
defer dir.close();
var iter = dir.iterate();
while (try iter.next()) |entry| {
if (entry.kind == .file and std.mem.endsWith(u8, entry.name, ".conf")) {
const path = try std.fs.path.join(allocator, &.{ dir_path, entry.name });
var walker = try dir.walk(allocator);
defer walker.deinit();
while (try walker.next()) |entry| {
if (entry.kind == .file and std.mem.endsWith(u8, entry.path, ".conf")) {
const path = try std.fs.path.join(allocator, &.{ dir_path, entry.path });
defer allocator.free(path);
const file = try std.fs.openFileAbsolute(path, .{});

View File

@ -288,7 +288,7 @@ pub fn Parser(comptime T: type) type {
}
},
.pointer => |ptr_info| {
if (ptr_info.size != .many) return error.InvalidSyntax;
if (ptr_info.size != .Slice) return error.InvalidSyntax;
switch (ptr_info.child) {
u8 => {
@field(result, field.name) = try self.parseString();

View File

@ -11,7 +11,6 @@ const scx_scheds = @import("clients/scx_scheds.zig");
pub const Daemon = struct {
allocator: std.mem.Allocator,
config_path: []const u8,
system_conf_path: []const u8,
profile_manager: ProfileManager,
oneshot: bool,
known_pids: ?std.AutoHashMap(u32, *const Profile),
@ -23,14 +22,11 @@ pub const Daemon = struct {
const Self = @This();
pub fn init(allocator: std.mem.Allocator, config_path: []const u8, system_conf_path: []const u8, oneshot: bool) !*Self {
pub fn init(allocator: std.mem.Allocator, config_path: []const u8, oneshot: bool) !*Self {
const config_path_owned = try allocator.dupe(u8, config_path);
errdefer allocator.free(config_path_owned);
const system_conf_path_owned = try allocator.dupe(u8, system_conf_path);
errdefer allocator.free(system_conf_path_owned);
const config = try Config.load(allocator, config_path, system_conf_path);
const config = try Config.load(allocator, config_path);
const power_profiles = try PowerProfiles.init(allocator, config);
errdefer power_profiles.deinit();
@ -40,7 +36,7 @@ pub const Daemon = struct {
}
var profile_manager = ProfileManager.init(allocator, power_profiles, config);
try ProfileLoader.loadProfiles(allocator, &profile_manager.profiles, &profile_manager.proton_profile, oneshot, config.profile_mode);
try ProfileLoader.loadProfiles(allocator, &profile_manager.profiles, &profile_manager.proton_profile, oneshot);
const current_time = std.time.nanoTimestamp();
try scx_scheds.init(allocator);
@ -48,16 +44,15 @@ pub const Daemon = struct {
const self = try allocator.create(Self);
self.* = .{
.allocator = allocator,
.config_path = config_path_owned,
.system_conf_path = system_conf_path_owned,
.profile_manager = profile_manager,
.oneshot = oneshot,
.known_pids = if (!oneshot) std.AutoHashMap(u32, *const Profile).init(allocator) else null,
.power_profiles = power_profiles,
.last_profiles_check = current_time,
.last_config_check = current_time,
.performance_mode = performance_mode,
.config = config,
.config_path = config_path_owned,
.performance_mode = performance_mode,
};
return self;
@ -72,7 +67,6 @@ pub const Daemon = struct {
}
self.config.deinit();
self.allocator.free(self.config_path);
self.allocator.free(self.system_conf_path);
self.allocator.destroy(self);
}
@ -89,11 +83,10 @@ pub const Daemon = struct {
self.power_profiles.deinit();
self.config.deinit();
const config = try Config.load(self.allocator, self.config_path, self.system_conf_path);
const config = try Config.load(self.allocator, self.config_path);
const power_profiles = try PowerProfiles.init(self.allocator, config);
var profile_manager = ProfileManager.init(self.allocator, power_profiles, config);
try ProfileLoader.loadProfiles(self.allocator, &profile_manager.profiles, &profile_manager.proton_profile, self.oneshot, config.profile_mode);
try ProfileLoader.loadProfiles(self.allocator, &profile_manager.profiles, &profile_manager.proton_profile, self.oneshot);
self.config = config;
self.power_profiles = power_profiles;
self.performance_mode = self.power_profiles.isPerformanceAvailable();

View File

@ -26,9 +26,6 @@ const AllocTracker = struct {
}
};
const config_path: []const u8 = "/etc/falcond/config.conf";
const system_conf_path: []const u8 = "/usr/share/falcond/system.conf";
fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8 {
var t: *AllocTracker = @ptrCast(@alignCast(ctx));
t.trackAlloc();
@ -49,6 +46,7 @@ fn free(ctx: *anyopaque, buf: []u8, log2_buf_align: u8, ret_addr: usize) void {
var gpa_vtable: *const std.mem.Allocator.VTable = undefined;
var gpa_ptr: *anyopaque = undefined;
pub fn main() !void {
std.log.info("Starting falcond...", .{});
@ -87,27 +85,8 @@ pub fn main() !void {
if (std.mem.eql(u8, arg, "--oneshot")) break true;
} else false;
try checkAndUpgradeConfig(allocator, config_path);
var daemon = try Daemon.init(allocator, config_path, system_conf_path, oneshot);
var daemon = try Daemon.init(allocator, "/etc/falcond/config.conf", oneshot);
defer daemon.deinit();
try daemon.run();
}
fn checkAndUpgradeConfig(allocator: std.mem.Allocator, conf_path: []const u8) !void {
const file = std.fs.openFileAbsolute(conf_path, .{}) catch |err| switch (err) {
error.FileNotFound => return,
else => return err,
};
defer file.close();
const content = try file.readToEndAlloc(allocator, std.math.maxInt(usize));
defer allocator.free(content);
if (std.mem.indexOf(u8, content, "system_processes = ")) |_| {
std.log.info("Upgrading config file to new format", .{});
file.close();
try std.fs.deleteFileAbsolute(config_path);
}
}

View File

@ -2,9 +2,8 @@ const std = @import("std");
const confloader = @import("../config/confloader.zig");
const types = @import("types.zig");
const Profile = types.Profile;
const ProfileMode = @import("../config/config.zig").ProfileMode;
pub fn loadProfiles(allocator: std.mem.Allocator, profiles: *std.ArrayList(Profile), proton_profile: *?*const Profile, oneshot: bool, mode: ProfileMode) !void {
pub fn loadProfiles(allocator: std.mem.Allocator, profiles: *std.ArrayList(Profile), proton_profile: *?*const Profile, oneshot: bool) !void {
if (oneshot) {
try profiles.append(Profile{
.name = try allocator.dupe(u8, "Hades3.exe"),
@ -20,14 +19,7 @@ pub fn loadProfiles(allocator: std.mem.Allocator, profiles: *std.ArrayList(Profi
proton_profile.* = &profiles.items[1];
} else {
const base_path = "/usr/share/falcond/profiles";
const profiles_path = if (mode != .none)
try std.fmt.allocPrint(allocator, "{s}/{s}", .{ base_path, @tagName(mode) })
else
base_path;
defer if (mode != .none) allocator.free(profiles_path);
var loaded_profiles = try confloader.loadConfDir(Profile, allocator, profiles_path);
var loaded_profiles = try confloader.loadConfDir(Profile, allocator, "/usr/share/falcond/profiles");
defer loaded_profiles.deinit();
try profiles.appendSlice(loaded_profiles.items);
@ -35,10 +27,11 @@ pub fn loadProfiles(allocator: std.mem.Allocator, profiles: *std.ArrayList(Profi
for (profiles.items) |*profile| {
if (std.mem.eql(u8, profile.name, "Proton")) {
proton_profile.* = profile;
std.log.info("Found Proton profile: {s}", .{profile.name});
break;
}
}
std.log.info("Loaded {d} profiles (mode: {s})", .{ profiles.items.len, @tagName(mode) });
std.log.info("Loaded {d} profiles", .{profiles.items.len});
}
}

View File

@ -2,7 +2,7 @@
set -e
VERSION="1.0.8"
VERSION="1.0.5"
source ./pika-build-config.sh