Compare commits

..

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

7 changed files with 37 additions and 253 deletions

View File

@ -1 +1 @@
3 1

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,22 +1,4 @@
falcond (1.0.9-101pika1) pika; urgency=low falcond (1.0.7-101pika1) pika; urgency=low
* Config parsing fixes
-- ferreo <ferreo@pika-os.com> Sun, 12 Jan 2025 13:48:00 +0300
falcond (1.0.8-101pika3) pika; urgency=low
* Update profiles
-- ferreo <ferreo@pika-os.com> Sun, 12 Jan 2025 13:48:00 +0300
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 * Don't load profiles in subfolders

View File

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

View File

@ -20,10 +20,7 @@ pub fn loadConf(comptime T: type, allocator: std.mem.Allocator, path: []const u8
if (bytes_read != size) return error.UnexpectedEOF; if (bytes_read != size) return error.UnexpectedEOF;
var parser = Parser(T).init(allocator, buffer); var parser = Parser(T).init(allocator, buffer);
return parser.parse() catch |err| { return try parser.parse();
std.log.err("Failed to parse config file '{s}': {s}", .{ path, @errorName(err) });
return err;
};
} }
pub fn loadConfDir(comptime T: type, allocator: std.mem.Allocator, dir_path: []const u8) !std.ArrayList(T) { pub fn loadConfDir(comptime T: type, allocator: std.mem.Allocator, dir_path: []const u8) !std.ArrayList(T) {
@ -46,10 +43,7 @@ pub fn loadConfDir(comptime T: type, allocator: std.mem.Allocator, dir_path: []c
defer allocator.free(content); defer allocator.free(content);
var parser = Parser(T).init(allocator, content); var parser = Parser(T).init(allocator, content);
const parsed = parser.parse() catch |err| { const parsed = try parser.parse();
std.log.err("Failed to parse config file '{s}': {s}", .{ path, @errorName(err) });
return err;
};
try result.append(parsed); try result.append(parsed);
} }
} }

View File

@ -81,33 +81,17 @@ pub fn Parser(comptime T: type) type {
return error.InvalidSyntax; return error.InvalidSyntax;
self.pos += 1; self.pos += 1;
var escaped = false; const start = self.pos;
var result = std.ArrayList(u8).init(self.allocator);
errdefer result.deinit();
while (self.pos < self.content.len) : (self.pos += 1) { while (self.pos < self.content.len) : (self.pos += 1) {
const c = self.content[self.pos]; switch (self.content[self.pos]) {
if (escaped) {
switch (c) {
'"' => try result.append('"'),
'\\' => try result.append('\\'),
'n' => try result.append('\n'),
'r' => try result.append('\r'),
't' => try result.append('\t'),
else => return error.InvalidSyntax,
}
escaped = false;
continue;
}
switch (c) {
'"' => { '"' => {
const str = try self.allocator.dupe(u8, self.content[start..self.pos]);
self.pos += 1; self.pos += 1;
return result.toOwnedSlice(); return str;
}, },
'\\' => { '\\' => return error.InvalidSyntax,
escaped = true; else => {},
},
else => try result.append(c),
} }
} }
return error.UnterminatedString; return error.UnterminatedString;
@ -178,10 +162,7 @@ pub fn Parser(comptime T: type) type {
return values.toOwnedSlice(); return values.toOwnedSlice();
} }
const str = self.parseString() catch |err| { const str = try self.parseString();
std.log.err("Failed to parse string at position {}: {s}", .{ self.pos, @errorName(err) });
return err;
};
try values.append(str); try values.append(str);
self.skipWhitespace(); self.skipWhitespace();
@ -193,10 +174,8 @@ pub fn Parser(comptime T: type) type {
self.pos += 1; self.pos += 1;
return values.toOwnedSlice(); return values.toOwnedSlice();
} }
std.log.err("Expected ',' or ']' but found '{c}' at position {}", .{ self.content[self.pos], self.pos });
return error.InvalidSyntax; return error.InvalidSyntax;
} }
std.log.err("Unterminated array at position {}", .{self.pos});
return error.InvalidSyntax; return error.InvalidSyntax;
} }
@ -279,16 +258,11 @@ pub fn Parser(comptime T: type) type {
@field(result, field.name) = @intCast(try self.parseNumber()); @field(result, field.name) = @intCast(try self.parseNumber());
}, },
.array => |array_info| { .array => |array_info| {
switch (@typeInfo(array_info.child)) { const array = try self.parseArray();
i64 => { if (array.len > array_info.len) return error.InvalidSyntax;
const array = try self.parseArray(); @field(result, field.name) = undefined;
if (array.len > array_info.len) return error.InvalidSyntax; var dest = &@field(result, field.name);
@field(result, field.name) = undefined; @memcpy(dest[0..array.len], array);
var dest = &@field(result, field.name);
@memcpy(dest[0..array.len], array);
},
else => return error.InvalidSyntax,
}
}, },
.@"enum" => { .@"enum" => {
const ident = try self.parseIdentifier(); const ident = try self.parseIdentifier();
@ -314,22 +288,18 @@ pub fn Parser(comptime T: type) type {
} }
}, },
.pointer => |ptr_info| { .pointer => |ptr_info| {
if (ptr_info.size != .slice) { if (ptr_info.size != .Slice) return error.InvalidSyntax;
return error.InvalidSyntax; switch (ptr_info.child) {
}
if (ptr_info.child == []const u8) {
@field(result, field.name) = try self.parseStringArray();
} else switch (ptr_info.child) {
u8 => { u8 => {
@field(result, field.name) = try self.parseString(); @field(result, field.name) = try self.parseString();
}, },
i64 => { i64 => {
@field(result, field.name) = try self.parseArray(); @field(result, field.name) = try self.parseArray();
}, },
else => { []const u8 => {
return error.InvalidSyntax; @field(result, field.name) = try self.parseStringArray();
}, },
else => return error.InvalidSyntax,
} }
}, },
else => return error.InvalidSyntax, else => return error.InvalidSyntax,

View File

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