1{ config, lib, pkgs, ... }:
2let
3 cfg = config.hardware.bluetooth;
4 package = cfg.package;
5
6 inherit (lib)
7 mkDefault mkEnableOption mkIf mkOption mkPackageOption
8 mkRenamedOptionModule mkRemovedOptionModule
9 concatStringsSep escapeShellArgs literalExpression
10 optional optionals optionalAttrs recursiveUpdate types;
11
12 cfgFmt = pkgs.formats.ini { };
13
14 defaults = {
15 General.ControllerMode = "dual";
16 Policy.AutoEnable = cfg.powerOnBoot;
17 };
18
19 hasDisabledPlugins = builtins.length cfg.disabledPlugins > 0;
20
21in
22{
23 imports = [
24 (mkRenamedOptionModule [ "hardware" "bluetooth" "config" ] [ "hardware" "bluetooth" "settings" ])
25 (mkRemovedOptionModule [ "hardware" "bluetooth" "extraConfig" ] ''
26 Use hardware.bluetooth.settings instead.
27
28 This is part of the general move to use structured settings instead of raw
29 text for config as introduced by RFC0042:
30 https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md
31 '')
32 ];
33
34 ###### interface
35
36 options = {
37
38 hardware.bluetooth = {
39 enable = mkEnableOption "support for Bluetooth";
40
41 hsphfpd.enable = mkEnableOption "support for hsphfpd[-prototype] implementation";
42
43 powerOnBoot = mkOption {
44 type = types.bool;
45 default = true;
46 description = "Whether to power up the default Bluetooth controller on boot.";
47 };
48
49 package = mkPackageOption pkgs "bluez" { };
50
51 disabledPlugins = mkOption {
52 type = types.listOf types.str;
53 default = [ ];
54 description = "Built-in plugins to disable";
55 };
56
57 settings = mkOption {
58 type = cfgFmt.type;
59 default = { };
60 example = {
61 General = {
62 ControllerMode = "bredr";
63 };
64 };
65 description = "Set configuration for system-wide bluetooth (/etc/bluetooth/main.conf).";
66 };
67
68 input = mkOption {
69 type = cfgFmt.type;
70 default = { };
71 example = {
72 General = {
73 IdleTimeout = 30;
74 ClassicBondedOnly = true;
75 };
76 };
77 description = "Set configuration for the input service (/etc/bluetooth/input.conf).";
78 };
79
80 network = mkOption {
81 type = cfgFmt.type;
82 default = { };
83 example = {
84 General = {
85 DisableSecurity = true;
86 };
87 };
88 description = "Set configuration for the network service (/etc/bluetooth/network.conf).";
89 };
90 };
91 };
92
93 ###### implementation
94
95 config = mkIf cfg.enable {
96 environment.systemPackages = [ package ]
97 ++ optional cfg.hsphfpd.enable pkgs.hsphfpd;
98
99 environment.etc."bluetooth/input.conf".source =
100 cfgFmt.generate "input.conf" cfg.input;
101 environment.etc."bluetooth/network.conf".source =
102 cfgFmt.generate "network.conf" cfg.network;
103 environment.etc."bluetooth/main.conf".source =
104 cfgFmt.generate "main.conf" (recursiveUpdate defaults cfg.settings);
105 services.udev.packages = [ package ];
106 services.dbus.packages = [ package ]
107 ++ optional cfg.hsphfpd.enable pkgs.hsphfpd;
108 systemd.packages = [ package ];
109
110 systemd.services = {
111 bluetooth =
112 let
113 # `man bluetoothd` will refer to main.conf in the nix store but bluez
114 # will in fact load the configuration file at /etc/bluetooth/main.conf
115 # so force it here to avoid any ambiguity and things suddenly breaking
116 # if/when the bluez derivation is changed.
117 args = [ "-f" "/etc/bluetooth/main.conf" ]
118 ++ optional hasDisabledPlugins
119 "--noplugin=${concatStringsSep "," cfg.disabledPlugins}";
120 in
121 {
122 wantedBy = [ "bluetooth.target" ];
123 aliases = [ "dbus-org.bluez.service" ];
124 serviceConfig.ExecStart = [
125 ""
126 "${package}/libexec/bluetooth/bluetoothd ${escapeShellArgs args}"
127 ];
128 # restarting can leave people without a mouse/keyboard
129 unitConfig.X-RestartIfChanged = false;
130 };
131 }
132 // (optionalAttrs cfg.hsphfpd.enable {
133 hsphfpd = {
134 after = [ "bluetooth.service" ];
135 requires = [ "bluetooth.service" ];
136 wantedBy = [ "bluetooth.target" ];
137
138 description = "A prototype implementation used for connecting HSP/HFP Bluetooth devices";
139 serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/hsphfpd.pl";
140 };
141 });
142
143 systemd.user.services = {
144 obex.aliases = [ "dbus-org.bluez.obex.service" ];
145 }
146 // optionalAttrs cfg.hsphfpd.enable {
147 telephony_client = {
148 wantedBy = [ "default.target" ];
149
150 description = "telephony_client for hsphfpd";
151 serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/telephony_client.pl";
152 };
153 };
154 };
155}