Quick-join Teams meetings from URL — paste link, click Join
Some checks failed
CI / build-and-test (push) Failing after 27s
Some checks failed
CI / build-and-test (push) Failing after 27s
Adds a small URL input + Join button to the IN-CALL bar. Operators paste a https://teams.microsoft.com/l/meetup-join/... or msteams:/l/meetup-join/... link, click Join, and Teams launches into the meeting in one shot. Eliminates the open-Teams → Calendar → find meeting → click join dance — operators get meeting links from email/Outlook and can now join straight from TeamsISO. TeamsLauncher.TryJoinMeeting validates the URL targets Teams (only http(s) URLs containing teams.microsoft.com / teams.live.com, or msteams: deep-links — won't shell-exec arbitrary clipboard contents). On success, integrates with AutoHideTeamsWindows so the Teams meeting window briefly appears then vanishes; operator is in the call, driving routing from TeamsISO. VM-side: MainViewModel.JoinMeetingCommand + JoinMeetingUrl two-way bound. Field clears on success; warn-toast on failure with the specific reason (empty / not-a-teams-url / launch-failed).
This commit is contained in:
parent
a9a10e01a4
commit
b9147183ce
3 changed files with 122 additions and 0 deletions
|
|
@ -624,6 +624,40 @@
|
|||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<!-- Quick-join: paste a Teams meeting URL, click Join,
|
||||
Teams launches into the meeting via the shell handler.
|
||||
Avoids the open-Teams → Calendar → find meeting → click
|
||||
join dance. Only enabled with non-empty text. -->
|
||||
<Border Width="1"
|
||||
Background="{DynamicResource Wd.Border}"
|
||||
Margin="14,4,12,4"/>
|
||||
<TextBox Text="{Binding JoinMeetingUrl, UpdateSourceTrigger=PropertyChanged}"
|
||||
Width="240"
|
||||
VerticalAlignment="Center"
|
||||
Padding="10,5"
|
||||
FontSize="11"
|
||||
ToolTip="Paste a Teams meeting URL (https://teams.microsoft.com/l/meetup-join/... or msteams:/l/meetup-join/...) and click Join. TeamsISO hands it to Teams to launch + join in one step."/>
|
||||
<Button Style="{StaticResource Wd.Button.Ghost}"
|
||||
Command="{Binding JoinMeetingCommand}"
|
||||
Padding="14,6"
|
||||
Margin="6,0,0,0"
|
||||
ToolTip="Hand the pasted meeting URL to Microsoft Teams. With auto-hide on (DISPLAY tab), the Teams window briefly appears then hides automatically.">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Data="M 8,1 L 8,11 M 4,7 L 8,11 L 12,7 M 1,14 L 15,14"
|
||||
Stroke="{DynamicResource Wd.Accent.Cyan}"
|
||||
StrokeThickness="1.5"
|
||||
Fill="Transparent"
|
||||
Width="16" Height="15"
|
||||
Stretch="None"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"/>
|
||||
<TextBlock Text="Join"
|
||||
Style="{StaticResource Wd.Text.Body}"
|
||||
FontSize="12"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
|
|
|
|||
|
|
@ -147,6 +147,58 @@ public static class TeamsLauncher
|
|||
return asked;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hand a meeting URL off to the Teams shell handler. Accepts both the
|
||||
/// <c>https://teams.microsoft.com/l/meetup-join/...</c> web format and
|
||||
/// the <c>msteams:/l/meetup-join/...</c> deep-link form (either causes
|
||||
/// Teams to launch + join the meeting in one shot — the OS shell maps
|
||||
/// teams.microsoft.com URLs to the registered ms-teams: handler).
|
||||
///
|
||||
/// Use case: operator pastes a meeting link they got over email / chat
|
||||
/// into TeamsISO's quick-join field instead of opening Teams,
|
||||
/// hunting down the calendar entry, and clicking Join. With auto-hide
|
||||
/// on, the Teams window flashes briefly then disappears; the operator
|
||||
/// is now in the meeting, driving routing from TeamsISO.
|
||||
///
|
||||
/// Returns true if the shell accepted the URL; false if URL is malformed
|
||||
/// or rejected. errorMessage populated on failure.
|
||||
/// </summary>
|
||||
public static bool TryJoinMeeting(string url, out string? errorMessage)
|
||||
{
|
||||
errorMessage = null;
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
{
|
||||
errorMessage = "URL is empty.";
|
||||
return false;
|
||||
}
|
||||
|
||||
var trimmed = url.Trim();
|
||||
|
||||
// Defensive sanity-check: only accept URLs that obviously target
|
||||
// Teams. We don't want to invoke arbitrary shell handlers from a
|
||||
// clipboard paste — if someone pastes "calc.exe" into the input we
|
||||
// shouldn't launch it. Specifically: http(s) URLs must contain
|
||||
// "teams.microsoft.com" or "teams.live.com"; otherwise must start
|
||||
// with "msteams:".
|
||||
var lower = trimmed.ToLowerInvariant();
|
||||
var looksLikeTeams =
|
||||
lower.StartsWith("msteams:") ||
|
||||
(lower.StartsWith("http://") || lower.StartsWith("https://")) &&
|
||||
(lower.Contains("teams.microsoft.com") || lower.Contains("teams.live.com"));
|
||||
if (!looksLikeTeams)
|
||||
{
|
||||
errorMessage = "Not a Microsoft Teams meeting URL. " +
|
||||
"Expected a https://teams.microsoft.com/l/meetup-join/... " +
|
||||
"or msteams:/l/meetup-join/... link.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryStart(trimmed, useShell: true, out var err))
|
||||
return true;
|
||||
errorMessage = err;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryStart(string target, bool useShell, out string error, string? arguments = null)
|
||||
{
|
||||
error = string.Empty;
|
||||
|
|
|
|||
|
|
@ -163,6 +163,22 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||
/// </summary>
|
||||
public AsyncRelayCommand RollRecordingCommand { get; }
|
||||
|
||||
/// <summary>Join a Teams meeting from a pasted URL — see <see cref="JoinMeetingUrl"/>.</summary>
|
||||
public RelayCommand JoinMeetingCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Two-way bound to the quick-join input. Whatever the operator pastes
|
||||
/// gets handed to <see cref="TeamsLauncher.TryJoinMeeting"/> when the
|
||||
/// Join button fires. Cleared on success so the field is ready for the
|
||||
/// next paste.
|
||||
/// </summary>
|
||||
public string JoinMeetingUrl
|
||||
{
|
||||
get => _joinMeetingUrl;
|
||||
set => SetField(ref _joinMeetingUrl, value);
|
||||
}
|
||||
private string _joinMeetingUrl = string.Empty;
|
||||
|
||||
public string StatusText
|
||||
{
|
||||
get => _statusText;
|
||||
|
|
@ -328,6 +344,26 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||
notes.Show(); // non-modal so operators can stamp + read alongside the show
|
||||
});
|
||||
|
||||
JoinMeetingCommand = new RelayCommand(() =>
|
||||
{
|
||||
// Trim + handle the operator pasting whitespace around the URL.
|
||||
var url = (_joinMeetingUrl ?? string.Empty).Trim();
|
||||
if (string.IsNullOrEmpty(url)) return;
|
||||
if (TeamsLauncher.TryJoinMeeting(url, out var error))
|
||||
{
|
||||
Toast.Show("Joining Teams meeting…");
|
||||
JoinMeetingUrl = string.Empty;
|
||||
// If the operator has auto-hide on, kick off the hide watcher
|
||||
// so the Teams meeting window goes away as soon as it renders.
|
||||
if (Settings.AutoHideTeamsWindows)
|
||||
_ = TeamsLauncher.AutoHideAfterLaunchAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
Toast.Warn($"Could not join: {error}");
|
||||
}
|
||||
});
|
||||
|
||||
RollRecordingCommand = new AsyncRelayCommand(RollRecordingAsync,
|
||||
() => _controller.RecordingEnabled && Participants.Any(p => p.IsEnabled));
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue