Asus Zenbook Duo (2024 / UX8406MA) & NixOS

This week the Zenbook Duo (UX8406MA) was released to the public. This is a neat little dual-screen laptop with a primary screen, and a secondary screen underneath a removable bluetooth keyboard & trackpad. I’ve been playing around with one for a couple of days on NixOS, and found that it’s pretty easy to get it working fairly smoothly and cleanly.

I’ve popped up a public spreadsheet of all the hardware that I’ve tested out, and some of the NixOS config tweaks (very minor) required to get it up and running.

Looking forward to getting some convenience elements configured in the future, like automatically enabling / disabling the secondary screen when the keyboard is placed onto the device. But I thought this information, as-is, could be really useful for anyone else trying to get this device online.

Here it is running in dual-screen pop-up mode, w/ NixOS + KDE, and the bluetooth keyboard in-front. I think this configuration (or a portrait-mode config) will be a great portable work configuration. It also has a built-in kickstand to support this:

6 Likes

Fantastic. It’d be amazing to see any improvements you wire up make their way into nixos-hardware.

I have that in-mind as I tackle the convenience parts of the config. So far the options used are pretty much “upgrade the kernel and turn things on” which probably doesn’t warrant support in nixos-hardware. :slight_smile:

1 Like

How did you manage to pair your keyboard? It doesn’t show up for me in the list when I’m trying to pair it. ASUS Pen paired just fine, so bluetooth works. I toggled the switch on the left of the keyboard towards bluetooth icon, the blue led on F10 is blinking slowly, and I tried holding Fn+F10, but that doesn’t change anything.

Nevermind, managed to connect it, works fine. I think you don’t need to hold Fn when trying to activate pairing. Regarding the touch and pen on the bottom display, it doesn’t work as a “touchpad”, it’s just mapped to the top screen for both pen and touch. I checked it with a drawing app and the cursor is not actually relative, it’s absolute (both for finger and for the pen), just isn’t mapped to the bottom screen. I use GNOME though, so it might be different for you on KDE.

2 Likes

I’ve wrote some scripts to automatically toggle screens (in GNOME), sync display brightness and set the battery charging threshold. See GitHub - alesya-h/zenbook-duo-2024-ux8406ma-linux: automatic screen on/off, brightness sync, battery limiter

Also, for me the keyboard now does provide volume control etc, but F1-F12 always require holding Fn. I don’t know how it happened but it may have involved fiddling with the Fn-Lock option in the UEFI settings and booting into windows. Now I can’t get back the normal F1-F12 keys behaviour :confused:

2 Likes

Sweet, thanks for sharing the scripts!

Interesting on the keyboard side… I’ve run through a few kernel hacks and printk() sessions and so far have reached the conclusion that the Fn-key functionality is implemented in some firmware that I don’t know how to operate/initialize/unlock. My next plan (when I find time) was to do a USB capture with a Windows system to see if I can figure out how to get it operating. But I didn’t realize/notice any UEFI settings… perhaps with a change of settings there it would operate differently in a way that I could make Linux identify. (I am a caveman with a club battering kernel modules with no idea how they operate, so, this could all be wrong.)

re: Screen brightness – I haven’t found any issue where the screen brightness isn’t synced between the two. I guess that could just be lucky with how KDE changes the brightness that it affects both screens. :sweat_smile:

Just a note, the main developer of asusctl DM’d me on Discord and mentioned that ASUS sent them (or would send them) a Zenbook Duo. They might be a good person to try to coordinate efforts with, though I don’t think they’re a NixOSer.

I think that project is a big reason that I’m so in love with my G14 2022, so I love to send them praise and attention. Maybe their Discord could be a good place for hints.

2 Likes

I am really excited you shared this. As soon as I saw this machine I wanted to throw Nix on it but was concerned about compatibility. I am considering picking this up as well and contributing to the discourse.

Thank you!

2 Likes

Nice job , very cool, lots of info!

Following this discussion with an eagle eye :slight_smile:

I am trying to order it , the 2880p version, because in belgium / netherlands they don’t seem to offer the 1920p version.

little questions:

  • Any update regarding the function keys (brighness,…) ?
  • I guess the libwacom fix will also be applicable for XFCE
  • weird that the wireless login does not work with lightdm
  • that the 2nd screen is always on always with lightdm , sounds a little annoying.
    With GDM it is not a problem, correct?

No update on the function keys from me. I’m a little out of my area of expertise on that problem – I’ve done multiple tweaked kernel builds dropping in a variety of debugging information, as well as performing some USB packet captures, and from what I can tell there is nothing that distinguishes the keypresses w & wo/ the Fn key pressed. That leads me to think there’s probably some kind of initialization that needs to happen on the device connect, but I haven’t been able to make time to continue working on it.

Don’t know about the libwacom fix; seems likely though.

Haven’t tested with GDM, but I would assume the results would come down to “does it support enabling and disabling different screens”. (and bluetooth, assuming that for the wireless you’re refering to the wireless keyboard).

2 Likes

excellent work, thanks ! Do any of you experience about auto flight mode when you pick up the keyboard ?

Yes, I used standalone wm, so I only saw wifi turning off, I turned it on manually afterwards and if I booted into system with keyboard detached, then putting it back on would trigger this. Don’t know how to fix it in first place, don’t want to rely on scripts. Also keyboard backlight doesn’t work no matter what I do

1 Like

I’ve just got mine, and am keen to get things working, starting with the keyboard.

I think it does seem to be initialised somehow by the MyAsus software, as there seems to be a period where it reconnects and enables the backlight during Windows startup. I think I’ll try running a Windows VM to capture the USB traffic as it starts up, assuming the software will even run in a VM.

Has anyone looked at the ACPI DSDT/SSDT tables yet? If we’re lucky, it may just be an ACPI call. Otherwise, we’ll need to reverse-engineer Bluetooth as well :frowning:

One last note is that the function call does seem to have some sort of firmware significance, as the insert key (Fn+Del) actually works fine.

1 Like

On another note, has anyone worked out how to turn off the content-aware brightness control (CABC) / Intel Display Power Saving Technology (DPST) in Linux?

There are settings for both in the Windows brightness settings and Intel Graphics Command Centre respectively, but I can’t work out how to disable it otherwise. There are no UEFI settings exposed in the UI, but I haven’t checked the setup variables yet.

Research indicates that Dell had a similar issue with a very aggressively-dimmed XPS a while back, and they released the display firmware update tool with an optional firmware to disable it. ASUS actually give us the firmware update tool already (in the service manual section of the downloads), but there’s only one firmware per screen.

It’s very distracting - you can see flickering when typing in a terminal, for example. At first I thought my unit was defective, and then I found the settings in Windows.

Turns out this is actually due to VRR and gamma differences at different refresh rates. It appears that KWin (or some other layer) doesn’t actually know how to disable VRR on Arc, it just pretends to do so.

Unfortunately, i915 doesn’t appear to let you disable VRR in an easy way. I’ll make a derivation/configuration to mod the EDIDs instead.

Third time lucky: Turns out the flickering can be caused by both VRR and Panel Self Refresh. The latter is the most significant culprit.

In summary: When PSR is enabled in both Windows and Linux, flickering occurs. To fix this, disable PSR. On Windows, this can be done in the power settings of the Intel Graphics Command Centre, and on Linux, i915.enable_psr=0 can be passed to the kernel. Both PSR 1 and PSR 2 have this issue.

VRR can also lead to gamma fluctuations, and should also be disabled if it becomes bothersome.

This is my display configuration so far for the boot process. It uses the maximum resolution everywhere and enables KMS as early as possible (with 120Hz for those beautiful logs).

I am investigating turning the lower display off in the initramfs - this can be done statically by adding a lowercase d to the end of the eDP-2 modedb argument, but it cannot be enabled at all after being disabled in this fashion (as far as I can tell). This may be useful to put in a specialisation if you’d rather not deal with the lower monitor in userspace at all, though.

I’m also looking into setting the brightness in the initramfs - it defaults to the maximum, and I’d rather not get the Plymouth password box burnt in to the middle of the screen. (Done: See this post.)

{
  boot = {
    loader.systemd-boot.consoleMode = "max";
    kernelParams =
      let
        efiMode = "2880x1800";
        drmMode = "${efiMode}@120";
      in
      [
        # https://docs.kernel.org/fb/efifb.html
        "video=efifb:${efiMode}"

        # https://docs.kernel.org/fb/modedb.html
        "video=eDP-1:${drmMode}" # Upper
        "video=eDP-2:${drmMode}" # Lower
      ];
    initrd.kernelModules = [ "i915" ]; # Early KMS
    plymouth.extraConfig = ''
      DeviceScale=2
    '';
  };
}

On an unrelated note, I got the NPU (a.k.a. VPU) firmware loading - I’ll leave the Nixpkgs upstreaming to someone with more time and patience than I, but it’s nice to have for the moment.

{
  hardware.firmware = [
    (
      let
        model = "37xx";
        version = "0.0";

        firmware = pkgs.fetchurl {
          url = "https://github.com/intel/linux-npu-driver/raw/v1.2.0/firmware/bin/vpu_${model}_v${version}.bin";
          hash = "sha256-qGhLLiBnOlmF/BEIGC7DEPjfgdLCaMe7mWEtM9uK1mo=";
        };
      in
      pkgs.runCommand "intel-vpu-firmware-${model}-${version}" { } ''
        mkdir -p "$out/lib/firmware/intel/vpu"
        cp '${firmware}' "$out/lib/firmware/intel/vpu/vpu_${model}_v${version}.bin"
      ''
    )
  ];
}

UPDATE (cannot post more than thrice in a row)

Alright, with a lot of experimentation I’ve got the keyboard hotkeys working over Bluetooth. Here’s what I’ve discovered so far:

  1. ASUS keyboards send hotkey events over custom HID reports. The ASUS HID driver turns these custom reports into standard keypresses, and is mostly compatible with this laptop too.
  2. The relevant HID descriptor is slightly different from what the driver expects, and so it needs to be modified. Luckily, a similar thing has already been done for previous detachable ASUS keyboards. Unfortunately, the logic is not exactly the same (the byte layout is a little different) - but it’s close enough, so I used it for a working prototype. The kernel can handle the slightly mangled resultant descriptor.
  3. The ASUS HID driver will prefer to use WMI for backlight control if it is available. This is not great for our usecase (I don’t think it works with Bluetooth or even USB - it’s probably there for legacy reasons), so this behaviour needs to be changed. There’s also no reason that the backlight should be disabled if the keyboard is plugged in to another ASUS laptop, either.
  4. The keyboard actually shows up as two HID devices: One for the keyboard itself, and one for the touchpad. Oddly, the function key by itself actually does generate HID reports - but on the touchpad device, for some reason. I don’t think any driver currently takes advantage of this.
  5. Over USB, the relevant hotkey HID report descriptors are missing entirely, which is why I haven’t been able to get it working. There’s also some sort of third HID device, but I can’t seem to generate any events from it. The function key reports over the touchpad are still available, though, so that might be an easy way to get similar functionality.
  6. I have been unable to get the backlight working in any capacity. This requires more investigation.

I think my next plan of action is to look at the USB HID descriptors and run some packet capture in Windows to see how it compares to Linux.

CC @mfenniak as you were working on this too - you were right about the initialization step, but luckily hid_asus can already do it.

Here’s my prototype kernel patch. Note that for development, I have been using CCache along with the technique to patch in-tree modules to keep things nice and fast (not that anything is particularly slow on this thing anyway!).

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 78cdfb8b9a7a..0450ad7424a8 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -84,6 +84,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
 #define QUIRK_MEDION_E1239T		BIT(10)
 #define QUIRK_ROG_NKEY_KEYBOARD		BIT(11)
 #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
+#define QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD	BIT(13)
 
 #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
 						 QUIRK_NO_INIT_REPORTS | \
@@ -835,7 +836,7 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
 	drvdata->input = input;
 
 	if (drvdata->enable_backlight &&
-	    !asus_kbd_wmi_led_control_present(hdev) &&
+	    (!asus_kbd_wmi_led_control_present(hdev) || (drvdata->quirks & QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD)) &&
 	    asus_kbd_register_leds(hdev))
 		hid_warn(hdev, "Failed to initialize backlight.\n");
 
@@ -897,7 +898,9 @@ static int asus_input_mapping(struct hid_device *hdev,
 		case 0xb3: asus_map_key_clear(KEY_PROG3);	break; /* Fn+Left next aura */
 		case 0x6a: asus_map_key_clear(KEY_F13);		break; /* Screenpad toggle */
 		case 0x4b: asus_map_key_clear(KEY_F14);		break; /* Arrows/Pg-Up/Dn toggle */
-
+		case 0x9c: asus_map_key_clear(KEY_F15);     break; /* Screen swap */
+		case 0x7e: asus_map_key_clear(KEY_F16);     break; /* Emoji panel */
+		case 0x86: asus_map_key_clear(KEY_F17);     break; /* MyASUS */
 
 		default:
 			/* ASUS lazily declares 256 usages, ignore the rest,
@@ -1183,17 +1186,20 @@ static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 		hid_info(hdev, "Fixing up Asus T100 keyb report descriptor\n");
 		rdesc[74] &= ~HID_MAIN_ITEM_CONSTANT;
 	}
-	/* For the T100CHI/T90CHI keyboard dock */
-	if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI)) {
+	/* For the T100CHI/T90CHI keyboard dock and Zenbook Duo 2024+ keyboards */
+	if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI | QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD)) {
 		int rsize_orig;
 		int offs;
 
 		if (drvdata->quirks & QUIRK_T100CHI) {
 			rsize_orig = 403;
 			offs = 388;
-		} else {
+		} else if (drvdata->quirks & QUIRK_T90CHI) {
 			rsize_orig = 306;
 			offs = 291;
+		} else if (drvdata->quirks & QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD) {
+			rsize_orig = 257;
+			offs = 176;
 		}
 
 		/*
@@ -1298,6 +1304,12 @@ static const struct hid_device_id asus_devices[] = {
 	 */
 	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
 		USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
+	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+	    USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406_KEYBOARD),
+		QUIRK_USE_KBD_BACKLIGHT | QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD },
+	{ HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC,
+	    USB_VENDOR_ID_ASUSTEK, BT_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406_KEYBOARD),
+	  	QUIRK_USE_KBD_BACKLIGHT | QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD },
 	{ }
 };
 MODULE_DEVICE_TABLE(hid, asus_devices);
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 828a5c022c64..8d9a3e29f28f 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -208,6 +208,8 @@
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD	0x1866
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2	0x19b6
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3	0x1a30
+#define USB_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406_KEYBOARD	0x1b2c
+#define BT_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406_KEYBOARD    0x1b2d
 #define USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD	0x196b
 #define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD	0x1869
 

4 Likes

Wow, what fantastic investigation and work!

Are you in touch with the asusctl/asusd maintainer/author? They mentioned they got a Zenbook Duo loaner from ASUS and was looking into it, but that was some many weeks ago now.

EDIT: I’d be curious for your thoughts on the hardware/experience. I’ve heard some conflicting things, especially with respect to the pins on the KB scratching tables when it’s used in dual screen mode.

Are you in touch with the asusctl/asusd maintainer/author? They mentioned they got a Zenbook Duo loaner from ASUS and was looking into it, but that was some many weeks ago now.

The asusctl issue tracker is indeed probably a better place to discuss this, so I’ve made a post there.

I’d be curious for your thoughts on the hardware/experience. I’ve heard some conflicting things, especially with respect to the pins on the KB scratching tables when it’s used in dual screen mode.

I’ve only had it for a few days, but here are some very obvious things that I wish all the suspiciously similar reviews would have mentioned:

  1. The system gets very warm, even in the balanced performance modes. The air blowing out either side can be uncomfortably hot, even when not much is happening - and the lower display gets fairly warm too.
  2. The hinges have more play to them than I would have liked. The upper display is wobbling slightly as I’m typing this now in laptop mode, and in dual-screen mode at work, the upper display wobbles fairly easily from desk vibrations.
  3. Due to the heat and hinge letdowns, I’ve taken to using the included sleeve’s stand feature when writing with the pen on the lower screen. Without it, the screen gets hot enough to be uncomfortable, and there’s flex as the display + my hand is supported by the fairly small hinge design. I don’t particularly enjoy writing on inclined surfaces, so this is a bit disappointing.
  4. As I described in my previous posts, there’s flickering when PSR is enabled. Luckily, both Windows and Linux let you turn this off, though at the cost of power savings.
  5. As mentioned in the infamous Reddit post, the keyboard is indeed slightly warped in the upper corners, and flexes a little when pressed. In practice, this isn’t an issue, though, as it falls flat within a few minutes of the system powering on. The warping on my unit is only noticeable when in laptop mode, and I’ve had no issues using it on a desk. My wooden desk at home has been fine so far in terms of scratches, but I haven’t used it there all that much.
  6. The ports are poorly placed. I’d have liked at least one USB port to have been on the right side, and putting the audio jack on the right is pure insanity. I’m not a huge fan of HDMI on the right either, but at least that can be mitigated with a USB-C adapter.
  7. There are some minor software gripes in Windows:
    • The display brightnesses can only be adjusted separately through ScreenXpert, and the only way to open ScreenXpert is through a floating button, which is exactly what you don’t want on an OLED display. Why can’t it be opened through a gesture like the on-screen keyboard!?
    • The on-screen keyboard is opened with a six-finger tap, but cannot be dismissed in the same way - the close button must be pressed instead. These control buttons can be glitchy when additional external displays are connected, and sometimes can only be pressed with the mouse cursor.
    • The emoji key cannot be remapped. I do not need to send emoji that often. The Bluetooth control key does not send any signal to the OS - I wish it would at least still send F10 when the function lock is on. I had a similar blank function key on my old laptop and enjoyed its constant availability.
    • Between the Intel Arc software, the Intel Graphics Command Centre, Windows Settings, and MyASUS, the same display settings are repeated everywhere. Confusingly, they do no always seem to update when changed in another location - so I have no idea if VRR is truly enabled, for example, by looking at any particular settings page.

All that being said, all the positive aspects in the reviews have been accurate, and this device is way better than my previous setup (AU$1,200 4K 2-in-1 with 8th gen Intel + AU$150 external touchscreen monitor). The only thing that hurts is the AU$3,600 price tag :neutral_face: - I expected better build quality, but this is a first-gen product I suppose.

I hope this helps - let me know if there’s anything else you want to know!

3 Likes

I’ve made a service to set the screen brightnesses in the initrd. This is especially useful for encrypted setups, as the brightness defaults to the maximum until systemd restores it to the last setting, which can only happen after decryption.

Unfortunately, I can’t seem to find any way to disable the smooth brightness transition, or wait until it ends, meaning that there may be a bit of a flicker if the password prompt shows up early enough.

I’ve chosen 50 and 0 for the top and bottom displays respectively - feel free to change them as you like. Note that the maximum is defined by /sys/devices/pci0000:00/0000:00:02.0/drm/card1/card1-eDP-1/intel_backlight/max_brightness, which is 400 on my 2880x1800 model.

This implementation requires systemd and udev to be enabled in the initrd. A similar thing could probably be done for non-systemd configurations by using boot.initrd.postDeviceCommands.

See my previous post for additional early-boot display configuration as well.

{
  boot.initrd = {
    kernelModules = [ "i915" ]; # Early KMS
    systemd.services.initrd-brightness = {
      unitConfig.DefaultDependencies = false;
      wantedBy = [ "initrd.target" ];
      requires = [
        ''sys-devices-pci0000:00-0000:00:02.0-drm-card1-card1\x2deDP\x2d1-intel_backlight.device''
        ''sys-devices-pci0000:00-0000:00:02.0-drm-card1-card1\x2deDP\x2d2-card1\x2deDP\x2d2\x2dbacklight.device''
      ];
      before = [ "plymouth-start.service" ];
      after = [
        ''sys-devices-pci0000:00-0000:00:02.0-drm-card1-card1\x2deDP\x2d1-intel_backlight.device''
        ''sys-devices-pci0000:00-0000:00:02.0-drm-card1-card1\x2deDP\x2d2-card1\x2deDP\x2d2\x2dbacklight.device''
      ];
      script = ''
        echo 50 > '/sys/devices/pci0000:00/0000:00:02.0/drm/card1/card1-eDP-1/intel_backlight/brightness'
        echo  0 > '/sys/devices/pci0000:00/0000:00:02.0/drm/card1/card1-eDP-2/card1-eDP-2-backlight/brightness'
      '';
    };
  };
}

Apologies for being relatively new to all of this and still learning, but I am wondering why some of the functionality is only available in certain desktop environments – I have been using Wayland on KDE and would love to see some of the features that were implemented on GNOME, but wouldn’t really like to have to switch desktop environments.

Is there a particular reason why this was made to work in one but not the other? Is it particularly more difficult or harder to do in KDE for some reason or is there some other aspect getting in the way of its implementation?

I could be totally mistaken, but it seems like the rfkill keypress should be relatively simple to filter and, if triggered, run some command to change the config of the monitors.

I admit saying that and actually implementing it are two very different things, I am unfamiliar with how automating display configuration modification is handled in KDE (or anywhere else for that matter), but I don’t see why the implementations would have to be terribly different.

Again, not trying to say that the work alesya-h did wasn’t great – it is and if I can’t implement it in KDE i will very likely use it myself – I was just wondering if there was a more systematic reason that it was GNOME specific.

ps. just got my computer the other day and am still going through the process of configuring NixOS on it, I look forward to following everything y’all are doing closely, and, if I am capable enough, maybe even helping out.