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 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 (lib.mdDoc "support for Bluetooth");
40
41 hsphfpd.enable = mkEnableOption (lib.mdDoc "support for hsphfpd[-prototype] implementation");
42
43 powerOnBoot = mkOption {
44 type = types.bool;
45 default = true;
46 description = lib.mdDoc "Whether to power up the default Bluetooth controller on boot.";
47 };
48
49 package = mkOption {
50 type = types.package;
51 default = pkgs.bluez;
52 defaultText = literalExpression "pkgs.bluez";
53 example = literalExpression "pkgs.bluezFull";
54 description = lib.mdDoc ''
55 Which BlueZ package to use.
56
57 ::: {.note}
58 Use the `pkgs.bluezFull` package to enable all
59 bluez plugins.
60 :::
61 '';
62 };
63
64 disabledPlugins = mkOption {
65 type = types.listOf types.str;
66 default = [ ];
67 description = lib.mdDoc "Built-in plugins to disable";
68 };
69
70 settings = mkOption {
71 type = cfgFmt.type;
72 default = { };
73 example = {
74 General = {
75 ControllerMode = "bredr";
76 };
77 };
78 description = lib.mdDoc "Set configuration for system-wide bluetooth (/etc/bluetooth/main.conf).";
79 };
80 };
81 };
82
83 ###### implementation
84
85 config = mkIf cfg.enable {
86 environment.systemPackages = [ package ]
87 ++ optional cfg.hsphfpd.enable pkgs.hsphfpd;
88
89 environment.etc."bluetooth/main.conf".source =
90 cfgFmt.generate "main.conf" (recursiveUpdate defaults cfg.settings);
91 services.udev.packages = [ package ];
92 services.dbus.packages = [ package ]
93 ++ optional cfg.hsphfpd.enable pkgs.hsphfpd;
94 systemd.packages = [ package ];
95
96 systemd.services = {
97 bluetooth =
98 let
99 # `man bluetoothd` will refer to main.conf in the nix store but bluez
100 # will in fact load the configuration file at /etc/bluetooth/main.conf
101 # so force it here to avoid any ambiguity and things suddenly breaking
102 # if/when the bluez derivation is changed.
103 args = [ "-f" "/etc/bluetooth/main.conf" ]
104 ++ optional hasDisabledPlugins
105 "--noplugin=${concatStringsSep "," cfg.disabledPlugins}";
106 in
107 {
108 wantedBy = [ "bluetooth.target" ];
109 aliases = [ "dbus-org.bluez.service" ];
110 serviceConfig.ExecStart = [
111 ""
112 "${package}/libexec/bluetooth/bluetoothd ${escapeShellArgs args}"
113 ];
114 # restarting can leave people without a mouse/keyboard
115 unitConfig.X-RestartIfChanged = false;
116 };
117 }
118 // (optionalAttrs cfg.hsphfpd.enable {
119 hsphfpd = {
120 after = [ "bluetooth.service" ];
121 requires = [ "bluetooth.service" ];
122 wantedBy = [ "bluetooth.target" ];
123
124 description = "A prototype implementation used for connecting HSP/HFP Bluetooth devices";
125 serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/hsphfpd.pl";
126 };
127 });
128
129 systemd.user.services = {
130 obex.aliases = [ "dbus-org.bluez.obex.service" ];
131 }
132 // optionalAttrs cfg.hsphfpd.enable {
133 telephony_client = {
134 wantedBy = [ "default.target" ];
135
136 description = "telephony_client for hsphfpd";
137 serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/telephony_client.pl";
138 };
139 };
140 };
141}