How to configure wired headphone detection

Hello! I recently installed NixOS for the first time on my laptop and I noticed that whenever I plug in my headphones into the headphone jack, there is no audio in both the speakers and through my headphones.

Upon installing pwvucontrol, I noticed it was because the audio controller wasn’t automatically switching profiles when I plug them in. Clicking on the profile with “Headphones” in the title caused audio to correctly play through my headphones, but I had to manually switch back to speakers when I was done with them.

This problem is apparent just for the headphone jack; Bluetooth, the built-in speakers, and even a wired connection through USB-C work and switch between each other perfectly fine.

How can I configure NixOS to automatically detect my wired headphones and change audio controller profiles when I plug my headphones into the headphone jack?

Below are the relevant lines in my Nix config as well as the output of wpctl status, both while I have my headphones plugged in but the “Speaker” profile still selected.

  # Enable sound with pipewire.
  services.pulseaudio.enable = false;
  security.rtkit.enable = true;
  services.pipewire = {
	enable = true;
    alsa.enable = true;
    alsa.support32Bit = true;
    pulse.enable = true;
    # If you want to use JACK applications, uncomment this
    # jack.enable = true;

    # use the example session manager (no others are packaged yet so this is enabled by default,
    # no need to redefine it in your config for now)
    #media-session.enable = true;
  };

PipeWire 'pipewire-0' [1.6.5, cocotreb@tpt14g5, cookie:1160273331]
 └─ Clients:
        34. .kwin_wayland-wrapped               [1.6.5, cocotreb@tpt14g5, pid:2205]
        44. pipewire                            [1.6.5, cocotreb@tpt14g5, pid:2399]
        45. libcanberra                         [1.6.5, cocotreb@tpt14g5, pid:2306]
        66. WirePlumber [export]                [1.6.5, cocotreb@tpt14g5, pid:26191]
        77. Floorp                              [1.6.5, cocotreb@tpt14g5, pid:20999]
        82.                                     [1.6.5, cocotreb@tpt14g5, pid:2306]
        83. .xdg-desktop-portal-wrapped         [1.6.5, cocotreb@tpt14g5, pid:2214]
        86. .plasmashell-wrapped                [1.6.5, cocotreb@tpt14g5, pid:2348]
        87. libcanberra                         [1.6.5, cocotreb@tpt14g5, pid:2348]
        88.                                     [1.6.5, cocotreb@tpt14g5, pid:2348]
        93. wpctl                               [1.6.5, cocotreb@tpt14g5, pid:28417]
        94.                                     [1.6.5, cocotreb@tpt14g5, pid:2306]
       103. Floorp                              [1.6.5, cocotreb@tpt14g5, pid:20999]
       108. .pwvucontrol-wrapped                [1.6.5, cocotreb@tpt14g5, pid:28162]
       141. WirePlumber                         [1.6.5, cocotreb@tpt14g5, pid:26191]

Audio
 ├─ Devices:
 │      58. Meteor Lake-P HD Audio Controller   [alsa]
 │
 ├─ Sinks:
 │      39. Meteor Lake-P HD Audio Controller HDMI / DisplayPort 2 Output [vol: 1.00]
 │  *   42. Meteor Lake-P HD Audio Controller Speaker [vol: 0.80]
 │      47. Meteor Lake-P HD Audio Controller HDMI / DisplayPort 1 Output [vol: 1.00]
 │      79. Meteor Lake-P HD Audio Controller HDMI / DisplayPort 3 Output [vol: 1.00]
 │
 ├─ Sources:
 │      35. Meteor Lake-P HD Audio Controller Stereo Microphone [vol: 1.00]
 │  *  115. Meteor Lake-P HD Audio Controller Digital Microphone [vol: 1.00]
 │
 ├─ Filters:
 │
 └─ Streams:
        62. Floorp
             38. output_FR       > Speaker:playback_FR	[init]
             59. output_FL       > Speaker:playback_FL	[init]

Video
 ├─ Devices:
 │      43. Integrated Camera                   [v4l2]
 │     100. Integrated Camera: Integrated C     [libcamera]
 │     142. Integrated Camera                   [v4l2]
 │
 ├─ Sinks:
 │
 ├─ Sources:
 │  *   37. Integrated Camera (V4L2)
 │
 ├─ Filters:
 │
 └─ Streams:

Settings
 └─ Default Configured Devices:
         0. Audio/Sink    alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__Speaker__sink

I had to add udev rule?

Hi, thx for the reply! Where exactly can I find information about adding a udev rule to get the desired behavior for my case? I have never had to mess with those before and I hadn’t even heard of udev rules until I looked it up after reading your post. Based on what I found, it seems to interact with the hardware at a really low level and I can’t figure out on my own how to use them to specifically configure wired headphone detection…

You can follow my possibly helpful adventure here:

You’ll need to identify the device, which you should be able to do with lsusb and then identity the /dev entry that needs the permissions adjusted with something like journalctl -f or dmesg. Since the UI for my DAC dongle is a web app, I was able to identify the device in chromium by going to chrome://device-log/, but I suspect that won’t apply to you.

I also have:

    pipewire = {
      enable = true;
      pulse.enable = true;
    };

I don’t know if enabling pulseaudio inside pipewire will make a difference, but it might be worth trying

Hey, I appreciate the pointers for udev and I certainly learned something new today! However, I found something that makes me doubt that the existing udev rules are the problem…

Just for fun, I decided to disable pipewire and switch to pulseaudio in my config, like so:

	services.pulseaudio.enable = true;
	services.pipewire.enable = false;
	security.rtkit.enable = true;
	 # services.pipewire = {
	 # enable = true;
	 #   alsa.enable = true;
	 #   alsa.support32Bit = true;
	 #   pulse.enable = true;
	 #   # If you want to use JACK applications, uncomment this
	 #   # jack.enable = true;
	 #
	 #   # use the example session manager (no others are packaged yet so this is enabled by default,
	 #   # no need to redefine it in your config for now)
	 #   #media-session.enable = true;
	 # };

And the behavior is completely different; the problem appears resolved as plugging in and removing my headphones does correctly toggle between the headphones and speakers, respectively. Given that pipewire is pretty much objectively better than pulseaudio, this is by no means a permanent solution. I do hope this steers us in the right direction for resolving the problem, though =)

I will continue messing with my config and will add another post if I find anything. Once again, thank you for the help so far!

I was just throwing some spaghetti at the wall: I had no issue plugging my IEMs into the 3.5mm headphone jack on my little framework 13.

I will suggest that services.pulseaudio.enable = true; is quite different from services.pipewire.enable = true; with services.pipewire.pulse.enable = true;. Just mentioning as pipewire is so great

Hi, I am not entirely sure how I did it, but I was able to get pipewire to behave with the audio jack, but might have something to do with me switching from KDE to Sway. I appreciate the help, and I will post on this thread again if anything changes.

Ah, well I’m glad that worked out. Apparently the cool kids are using Niri and Hyperland these days, but I prefer the network transparency of X11 over the HDR of Wayland, and I am happier with XMonad as a replacement for KWin with plasma underneath.

Pointless GUI taste aside, this leaves me with a feeling of unease as switching the window manager shouldn’t have an effect on audio wiring.

I think what would be really nice to have would be see what the changes to the configuration.nix would result in on the config files on the system, or at least what the what configuration.nix would generate.

I get I don’t worry too much about what ghc or gcc is going to generate, except in critical loops, but nix-lang feels a bit more loosey-goosey, and it would be nice to see what changes to nftables changes to my configuration.nix would generate, rather than give up and just use a manual nftable configure file.

I’ve been playing around with A12/Arcan with Durden as the WM. It’s an interesting experience writing lua extensions to control windows placement.

I think it’s more that KDE runs a suite of services to monitor hardware hotplug, and switching audio outputs is one of the services it supplies (poorly, in this case).

I think this is what tools like dix are supposed to help with, at a high level. At the low level of a particular config file, you can run something like

$ nixos-rebuild build
$ diff {/run/current-system,./result}/etc/nftables.conf