Project 01

Konduit

In development

Cast your Linux desktop to a TV, projector or classroom display.

A native KDE Plasma app for Miracast (Wi-Fi Display) receivers.

Free and open source, under the GNU GPL version 2 or later. The source code will be published with the first release.

THE ROOMYOUR LAPTOPTHE DISPLAYWI-FI DIRECTNO CABLES · NO INTERNET
Fig. 1 · A direct link

At a glance

Platform
Linux (CachyOS), KDE Plasma 6 on Wayland
Works with
Miracast (Wi-Fi Display) receivers: TVs, projectors and classroom adapters
Built with
C++, Qt 6 and KDE Frameworks 6
Status
Early development. Used day to day on Arch Linux / CachyOS and tested with ScreenBeam classroom adapters.
License
GNU GPL version 2 or later (GPL-2.0-or-later)
Source code
To be announced
Download
To be announced

Screenshots

Select a screenshot to see it full size.

  • Konduit: Find a display
    Find a display Nearby displays with favourites, search and colour-coded signal quality.
  • Konduit: Casting
    Casting Live details while casting: resolution, encoder, bitrate and dropped frames.
  • Konduit: Choose what to share
    Choose what to share The whole screen, a single window, or extend the desktop onto the display.
  • Konduit: Quick options
    Quick options Picture quality, Smooth Motion, sound, automatic reconnect and the preview window, one click away.
  • Konduit: Quick connect
    Quick connect Meta+Shift+K opens a small picker of nearby displays.
  • Konduit: Preview window
    Preview window When extending the desktop, a live preview shows what the display is showing.
  • Konduit: Test pattern
    Test pattern Colour bars and a clock that flashes with an audible tick, for checking picture and lip sync.
  • Konduit: Casting settings
    Casting settings What to share, picture quality, sound and quick connect, each explained in plain language.
  • Konduit: Picture and encoding
    Picture and encoding Encoder, bitrate, keyframes and resolution, for enthusiasts.
  • Konduit: Readiness Check
    Readiness Check A read-only check of the computer that explains what is worth a look, with commands to copy.
  • Konduit: NetworkManager patch
    NetworkManager patch Optional, step-by-step help for displays such as ScreenBeam adapters, including what it costs and how to undo it.
  • Konduit: About Konduit
    About Konduit Version, license and the libraries in use.

Casting, without the cables

Konduit casts your desktop to Miracast receivers: TVs, projectors and classroom adapters such as Actiontec ScreenBeam. It connects directly over Wi-Fi Direct, so no cables, dongles or internet connection are needed, and nothing leaves the room.

Casting to these displays has long been easy on Windows and largely missing on Linux. Comparable Linux tools tend to find displays but struggle to connect to them. Konduit is built to connect reliably, and to explain clearly when something on the computer needs attention.

What makes it different

  • Connects reliably

    It is built to connect, not just to find displays. If the display drops, even through an adapter reboot, Konduit reconnects on its own.

  • Says what needs attention

    When something on the computer needs a look, Konduit explains it in plain language, with commands to copy. It never blocks you from connecting.

  • Native to Plasma

    Written from scratch in C++ with Qt 6 and KDE Frameworks 6, for Plasma 6 on Wayland.

  • Light on the laptop

    Automatic hardware encoding, where the display supports it, keeps CPU use low.

Features today

  • Find and connect

    Nearby displays with search, sorting by name or signal strength, pinned favourites and colour-coded signal quality.

  • Quick connect

    A global shortcut, Meta+Shift+K, opens a small picker, and a tray icon offers your favourites and the last display used.

  • Three ways to share

    The whole screen, a single window, or extend the desktop onto the display as a new screen.

  • Preview window

    When extending, a live preview can be pinned above other windows, can highlight the mouse pointer (adjustable size), and has a disconnect button.

  • Sound

    Desktop audio plays on the display while the laptop stays quiet, with adjustable audio delay.

  • Picture

    Quality presets, a 1080p60 Smooth Motion mode, and automatic hardware encoding where the display supports it, for low CPU use.

  • Test pattern

    Colour bars, a timer and a clock that flashes with an audible tick, for checking picture and lip sync.

  • Advanced settings

    Encoder, bitrate, resolution, scaling, keyframes and more, for enthusiasts. Settings that can safely change mid-cast apply live.

  • Reliability

    Automatic reconnect if the display drops. The display blanks when the computer locks, the computer stays awake while casting, and there's a clean hand-off whenever casting ends.

  • Privacy and security

    Everything stays on a direct local link, and Konduit only accepts a connection from the display it is casting to.

  • Readiness Check

    A friendly, read-only check of NetworkManager, Wi-Fi card, firewall, screen sharing, sound and encoding. It runs on first launch and explains what to look at, with commands to copy.

  • Optional helper

    For a NetworkManager limitation that stops some displays, such as ScreenBeam, from connecting: the patch, a commented build script and step-by-step instructions, including how to undo it. Konduit never changes the system on its own.

  • konduitctl

    A command-line companion for scanning, connecting and running the readiness check.

The NetworkManager patch

Some displays, such as ScreenBeam classroom adapters, already run their own Wi-Fi Direct group and expect a computer to join it, as Windows and Android do. NetworkManager always asks to start a new group instead. These displays answer, but never finish setting it up, so the connection gives up after 45 seconds.

This small patch to NetworkManager checks whether the display already runs a group and, if it does, joins it. Displays that don't keep connecting exactly as before. It is written to be offered to NetworkManager itself; until it's part of a release, Konduit includes an optional, commented script that builds Arch's own NetworkManager package with the patch added.

Needed for
Displays that run their own group, such as Actiontec ScreenBeam adapters. Other displays work without it.
Tested
NetworkManager 1.58.1 with a ScreenBeam 1000 EDU: connects in about five seconds, where it times out without the patch.
Build
About 3 minutes with --fast on an 8-thread laptop. Konduit never changes the system on its own.
Undo
sudo pacman -S networkmanager libnm, then sudo systemctl restart NetworkManager
wifi-p2p-join-group-owner.diff · 6 files, +44 −4 Download
diff --git a/src/core/devices/wifi/nm-device-wifi-p2p.c b/src/core/devices/wifi/nm-device-wifi-p2p.c
index 0d27017..df287bd 100644
--- a/src/core/devices/wifi/nm-device-wifi-p2p.c
+++ b/src/core/devices/wifi/nm-device-wifi-p2p.c
@@ -601,6 +601,7 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
     NMSettingWirelessSecurityWpsMethod wps_method;
     const char                        *wps_pin;
     const char                        *wps_method_str;
+    gboolean                           join;
 
     if (nm_clear_g_source_inst(&priv->find_peer_timeout_source))
         nm_assert_not_reached();
@@ -677,10 +678,20 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
         break;
     }
 
+    /* A peer that already runs a group, such as a Miracast sink operating as
+     * an autonomous or persistent GO, may answer GO negotiation without ever
+     * enabling its WPS registrar, so the connection would time out. Join its
+     * group instead: wpa_supplicant sends a Provision Discovery Request and
+     * then connects to the GO, which is what such peers expect. */
+    join = nm_wifi_p2p_peer_get_is_group_owner(peer);
+    if (join)
+        _LOGD(LOGD_WIFI, "Activation: (wifi-p2p) peer is a group owner, joining its group");
+
     nm_supplicant_interface_p2p_connect(priv->mgmt_iface,
                                         nm_wifi_p2p_peer_get_supplicant_path(peer),
                                         wps_method_str,
-                                        wps_pin);
+                                        wps_pin,
+                                        join);
 
     /* Set up a timeout on the connect attempt */
     if (!priv->sup_timeout_source) {
diff --git a/src/core/devices/wifi/nm-wifi-p2p-peer.c b/src/core/devices/wifi/nm-wifi-p2p-peer.c
index f4d2409..0c2abe1 100644
--- a/src/core/devices/wifi/nm-wifi-p2p-peer.c
+++ b/src/core/devices/wifi/nm-wifi-p2p-peer.c
@@ -55,6 +55,8 @@ struct _NMWifiP2PPeerPrivate {
 
     guint16 wps_config_methods;
 
+    guint8 group_capab;
+
     guint8 strength;
 
     NM80211ApFlags flags; /* General flags */
@@ -311,6 +313,15 @@ nm_wifi_p2p_peer_set_wps_config_methods(NMWifiP2PPeer *peer, guint16 wps_config_
     return TRUE;
 }
 
+gboolean
+nm_wifi_p2p_peer_get_is_group_owner(const NMWifiP2PPeer *peer)
+{
+    g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), FALSE);
+
+    /* Group Capability bit 0: "P2P Group Owner" */
+    return NM_FLAGS_HAS(NM_WIFI_P2P_PEER_GET_PRIVATE(peer)->group_capab, 0x01);
+}
+
 const char *const *
 nm_wifi_p2p_peer_get_groups(const NMWifiP2PPeer *peer)
 {
@@ -440,6 +451,9 @@ nm_wifi_p2p_peer_update_from_properties(NMWifiP2PPeer *peer, const NMSupplicantP
     changed |= nm_wifi_p2p_peer_set_wps_config_methods(peer, peer_info->config_methods);
     changed |= nm_wifi_p2p_peer_set_last_seen(peer, peer_info->last_seen_msec / 1000u);
 
+    /* Only used internally, to join a group that the peer already runs. */
+    priv->group_capab = peer_info->group_capab;
+
     /* We currently only use the groups information internally to check if
      * the peer is still joined. */
     if (!nm_strv_equal(priv->groups, peer_info->groups)) {
diff --git a/src/core/devices/wifi/nm-wifi-p2p-peer.h b/src/core/devices/wifi/nm-wifi-p2p-peer.h
index dc7178f..b83450f 100644
--- a/src/core/devices/wifi/nm-wifi-p2p-peer.h
+++ b/src/core/devices/wifi/nm-wifi-p2p-peer.h
@@ -77,6 +77,8 @@ gboolean nm_wifi_p2p_peer_set_wps_config_methods(NMWifiP2PPeer *peer, guint16 wp
 
 const char *const *nm_wifi_p2p_peer_get_groups(const NMWifiP2PPeer *peer);
 
+gboolean nm_wifi_p2p_peer_get_is_group_owner(const NMWifiP2PPeer *peer);
+
 const char    *nm_wifi_p2p_peer_get_address(const NMWifiP2PPeer *peer);
 gboolean       nm_wifi_p2p_peer_set_address(NMWifiP2PPeer *peer, const char *addr);
 gint8          nm_wifi_p2p_peer_get_strength(NMWifiP2PPeer *peer);
diff --git a/src/core/supplicant/nm-supplicant-interface.c b/src/core/supplicant/nm-supplicant-interface.c
index 3b85979..1f0d75e 100644
--- a/src/core/supplicant/nm-supplicant-interface.c
+++ b/src/core/supplicant/nm-supplicant-interface.c
@@ -1009,6 +1009,13 @@ _peer_info_properties_changed(NMSupplicantInterface *self,
             peer_info->config_methods = v_u16;
     }
 
+    {
+        guint8 v_u8;
+
+        if (nm_g_variant_lookup(properties, "groupcapability", "y", &v_u8))
+            peer_info->group_capab = v_u8;
+    }
+
     _peer_info_changed_emit(self, peer_info, TRUE);
 }
 
@@ -3072,7 +3079,8 @@ void
 nm_supplicant_interface_p2p_connect(NMSupplicantInterface *self,
                                     const char            *peer,
                                     const char            *wps_method,
-                                    const char            *wps_pin)
+                                    const char            *wps_pin,
+                                    gboolean               join)
 {
     GVariantBuilder builder;
 
@@ -3084,7 +3092,7 @@ nm_supplicant_interface_p2p_connect(NMSupplicantInterface *self,
     if (wps_pin)
         g_variant_builder_add(&builder, "{sv}", "pin", g_variant_new_string(wps_pin));
     g_variant_builder_add(&builder, "{sv}", "peer", g_variant_new_object_path(peer));
-    g_variant_builder_add(&builder, "{sv}", "join", g_variant_new_boolean(FALSE));
+    g_variant_builder_add(&builder, "{sv}", "join", g_variant_new_boolean(join));
     g_variant_builder_add(&builder, "{sv}", "persistent", g_variant_new_boolean(FALSE));
     g_variant_builder_add(&builder, "{sv}", "go_intent", g_variant_new_int32(7));
 
diff --git a/src/core/supplicant/nm-supplicant-interface.h b/src/core/supplicant/nm-supplicant-interface.h
index 0a51a61..66f9130 100644
--- a/src/core/supplicant/nm-supplicant-interface.h
+++ b/src/core/supplicant/nm-supplicant-interface.h
@@ -188,7 +188,8 @@ void nm_supplicant_interface_p2p_set_device_name(NMSupplicantInterface *self, co
 void nm_supplicant_interface_p2p_connect(NMSupplicantInterface *self,
                                          const char            *peer,
                                          const char            *wps_method,
-                                         const char            *wps_pin);
+                                         const char            *wps_pin,
+                                         gboolean               join);
 void nm_supplicant_interface_p2p_cancel_connect(NMSupplicantInterface *self);
 void nm_supplicant_interface_p2p_disconnect(NMSupplicantInterface *self);
 
diff --git a/src/core/supplicant/nm-supplicant-types.h b/src/core/supplicant/nm-supplicant-types.h
index 585dcfb..b17bdcb 100644
--- a/src/core/supplicant/nm-supplicant-types.h
+++ b/src/core/supplicant/nm-supplicant-types.h
@@ -225,6 +225,10 @@ typedef struct _NMSupplicantPeerInfo {
 
     guint16 config_methods;
 
+    /* P2P Group Capability bitmap (Wi-Fi P2P spec, 4.1.4); bit 0 is
+     * "P2P Group Owner", set while the peer runs a group. */
+    guint8 group_capab;
+
     guint8 address[6 /* ETH_ALEN */];
 
     gint8 signal_percent;

Coming next

  • Contemplating a public release, packaged for Arch Linux / CachyOS (AUR).
  • Getting the NetworkManager fix accepted upstream, so no patch is needed.
  • Chromecast / Google Cast support.