Global hotkeys, in TeamSpeak's own shape
Bindings are captured system-wide rather than only while the window has focus: X11's RECORD extension where the X server sees every key, and a /dev/input reader as the Wayland fallback. Any key can act as a modifier, mouse buttons included, as TS3 allows. The action catalogue, its three categories and the "advanced actions" split are reverse-engineered from the original client; actions this client cannot perform are listed but greyed out. Push-to-talk becomes one of these hotkeys, so the old focus-bound pushToTalkKey setting is gone and the Voice Activation button edits that binding instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,8 @@ import com.ts3client.audio.VoiceOutput;
|
||||
import com.ts3client.audio.desktop.AudioCapture;
|
||||
import com.ts3client.audio.desktop.AudioDevices;
|
||||
import com.ts3client.config.Settings;
|
||||
import com.ts3client.hotkey.Hotkey;
|
||||
import com.ts3client.hotkey.HotkeyAction;
|
||||
import com.ts3client.sound.SoundNotifier;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
@@ -30,8 +32,6 @@ import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -53,11 +53,13 @@ public final class SettingsDialog extends JDialog {
|
||||
private final VoiceInput liveMic;
|
||||
private final VoiceOutput livePlayback;
|
||||
private final SoundNotifier sounds;
|
||||
private final HotkeyService hotkeys;
|
||||
private final Runnable onApply;
|
||||
|
||||
private NotificationsPanel notificationsPanel;
|
||||
private IconPackPanel iconPackPanel;
|
||||
private ClientVersionPanel clientVersionPanel;
|
||||
private HotkeysPanel hotkeysPanel;
|
||||
|
||||
private JComboBox<AudioDevices.Device> inputCombo;
|
||||
private JComboBox<AudioDevices.Device> outputCombo;
|
||||
@@ -79,7 +81,6 @@ public final class SettingsDialog extends JDialog {
|
||||
private JLabel speechLabel;
|
||||
private LevelMeter meter;
|
||||
private JButton pttKeyButton;
|
||||
private int pttKey;
|
||||
private JSlider bitrateSlider;
|
||||
private JLabel bitrateLabel;
|
||||
private JSlider complexitySlider;
|
||||
@@ -92,14 +93,14 @@ public final class SettingsDialog extends JDialog {
|
||||
|
||||
public SettingsDialog(Frame owner, Settings settings,
|
||||
VoiceInput liveMic, VoiceOutput livePlayback,
|
||||
SoundNotifier sounds, Runnable onApply) {
|
||||
SoundNotifier sounds, HotkeyService hotkeys, Runnable onApply) {
|
||||
super(owner, "Options", true);
|
||||
this.settings = settings;
|
||||
this.liveMic = liveMic;
|
||||
this.livePlayback = livePlayback;
|
||||
this.sounds = sounds;
|
||||
this.onApply = onApply;
|
||||
this.pttKey = settings.pushToTalkKey;
|
||||
this.hotkeys = hotkeys;
|
||||
|
||||
JTabbedPane tabs = new JTabbedPane();
|
||||
tabs.addTab("Playback / Capture", scrollable(buildDevicesTab()));
|
||||
@@ -108,6 +109,8 @@ public final class SettingsDialog extends JDialog {
|
||||
tabs.addTab("Notifications", notificationsPanel);
|
||||
iconPackPanel = new IconPackPanel(settings);
|
||||
tabs.addTab("Design", iconPackPanel);
|
||||
hotkeysPanel = new HotkeysPanel(hotkeys);
|
||||
tabs.addTab("Hotkeys", hotkeysPanel);
|
||||
clientVersionPanel = new ClientVersionPanel(settings);
|
||||
tabs.addTab("Client Version", scrollable(clientVersionPanel));
|
||||
|
||||
@@ -313,9 +316,10 @@ public final class SettingsDialog extends JDialog {
|
||||
});
|
||||
addRow(p, c, row++, new JLabel("Speech threshold:"), sliderWithLabel(speechSlider, speechLabel));
|
||||
|
||||
pttKeyButton = new JButton(keyName(pttKey));
|
||||
pttKeyButton.addActionListener(e -> capturePttKey());
|
||||
addRow(p, c, row++, new JLabel("Push-to-talk key:"), pttKeyButton);
|
||||
pttKeyButton = new JButton(pushToTalkHotkeyText());
|
||||
pttKeyButton.setToolTipText("Push-to-talk is a global hotkey; this opens its binding");
|
||||
pttKeyButton.addActionListener(e -> editPushToTalkHotkey());
|
||||
addRow(p, c, row++, new JLabel("Push-to-talk hotkey:"), pttKeyButton);
|
||||
|
||||
vadOverPttCheck = new JCheckBox("Also detect voice while push-to-talk", settings.vadOverPtt);
|
||||
c.gridx = 0;
|
||||
@@ -511,23 +515,31 @@ public final class SettingsDialog extends JDialog {
|
||||
if (liveMic != null) liveMic.setOpusParameters(currentOpusParameters());
|
||||
}
|
||||
|
||||
private void capturePttKey() {
|
||||
pttKeyButton.setText("Press a key…");
|
||||
pttKeyButton.requestFocusInWindow();
|
||||
KeyAdapter ka = new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
pttKey = e.getKeyCode();
|
||||
pttKeyButton.setText(keyName(pttKey));
|
||||
pttKeyButton.removeKeyListener(this);
|
||||
}
|
||||
};
|
||||
pttKeyButton.addKeyListener(ka);
|
||||
private String pushToTalkHotkeyText() {
|
||||
Hotkey ptt = hotkeys.find(HotkeyAction.PTT_ACTIVATE);
|
||||
return ptt == null ? "No hotkey assigned" : hotkeys.display(ptt.combo);
|
||||
}
|
||||
|
||||
private static String keyName(int code) {
|
||||
String t = KeyEvent.getKeyText(code);
|
||||
return (t == null || t.isEmpty()) ? ("Key " + code) : t;
|
||||
/**
|
||||
* Push-to-talk is an ordinary hotkey, so this shortcut edits that binding — adding
|
||||
* it when there is none — rather than keeping a key of its own.
|
||||
*/
|
||||
private void editPushToTalkHotkey() {
|
||||
Hotkey existing = hotkeys.find(HotkeyAction.PTT_ACTIVATE);
|
||||
HotkeyDialog dlg = new HotkeyDialog(this, hotkeys,
|
||||
existing == null ? new Hotkey(HotkeyAction.PTT_ACTIVATE, null) : existing);
|
||||
dlg.setVisible(true);
|
||||
if (!dlg.isConfirmed()) return;
|
||||
List<Hotkey> updated = new java.util.ArrayList<>();
|
||||
synchronized (hotkeys.all()) {
|
||||
for (Hotkey h : hotkeys.all()) {
|
||||
if (h != existing) updated.add(h.copy());
|
||||
}
|
||||
}
|
||||
updated.add(dlg.result());
|
||||
hotkeys.replaceAll(updated);
|
||||
pttKeyButton.setText(pushToTalkHotkeyText());
|
||||
hotkeysPanel.reload();
|
||||
}
|
||||
|
||||
private void apply() {
|
||||
@@ -546,7 +558,6 @@ public final class SettingsDialog extends JDialog {
|
||||
settings.vadThresholdDb = thresholdSlider.getValue();
|
||||
settings.speechThreshold = speechSlider.getValue() / 100.0;
|
||||
settings.vadOverPtt = vadOverPttCheck.isSelected();
|
||||
settings.pushToTalkKey = pttKey;
|
||||
settings.bitrate = bitrateSlider.getValue() * 1000;
|
||||
settings.complexity = complexitySlider.getValue();
|
||||
settings.vbr = vbrCheck.isSelected();
|
||||
@@ -555,6 +566,7 @@ public final class SettingsDialog extends JDialog {
|
||||
notificationsPanel.apply();
|
||||
iconPackPanel.apply();
|
||||
clientVersionPanel.apply();
|
||||
hotkeysPanel.apply();
|
||||
settings.save();
|
||||
|
||||
if (liveMic != null) {
|
||||
|
||||
Reference in New Issue
Block a user