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.