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