Entrega v1 aula: shell com abas, PITIUPI_HOME e plugins de referência.
Consolida núcleo (presença, peer actions, MessageDispatcher), chat e screencast na galeria, documentação PT-BR e testes JUnit; remove mpChat do catálogo em favor do chat unificado. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
b10c196fc9
commit
eeb1936f6d
178 files changed
+7641
-1481
No files matched your search
@@ -0,0 +1 @@
|
||||
pitiupi.screenCast.plugin.ScreenCastPlugin
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../../pitiupi/dist/pitiupi.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
|
||||
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
|
||||
*/
|
||||
package pitiupi.screenCast.GUI;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import pitiupi.models.Menus.MulticastMenus.MulticastActionMenu;
|
||||
import pitiupi.models.Menus.MulticastMenus.MulticastMenu;
|
||||
import pitiupi.models.Menus.MulticastMenus.MulticastSubMenu;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author flavio
|
||||
*/
|
||||
public class ScreenCastMenu extends MulticastSubMenu {
|
||||
|
||||
private final ScreenCastShowMenu showMenu;
|
||||
|
||||
public ScreenCastMenu(ScreenCastPanel screenCastPanel) {
|
||||
this.showMenu = new ScreenCastShowMenu(screenCastPanel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return "Screen Cast";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MulticastMenu> getMenus() {
|
||||
return List.of(showMenu);
|
||||
}
|
||||
|
||||
private static final class ScreenCastShowMenu extends MulticastActionMenu {
|
||||
private final ScreenCastPanel screenCastPanel;
|
||||
|
||||
ScreenCastShowMenu(ScreenCastPanel screenCastPanel) {
|
||||
this.screenCastPanel = screenCastPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return "ScreenCast Window";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
screenCastPanel.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package pitiupi.screenCast.GUI;
|
||||
|
||||
import pitiupi.presentation.shell.PluginSessionKeys;
|
||||
|
||||
/**
|
||||
* Abas em que o usuário pode ligar envio de tela (outbound).
|
||||
*/
|
||||
final class ScreenCastOutboundTabs {
|
||||
|
||||
private ScreenCastOutboundTabs() {
|
||||
}
|
||||
|
||||
static boolean isOutboundShareTabKey(String key) {
|
||||
if (key == null || key.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
if (PluginSessionKeys.PEER_DRAFT.equals(key)) {
|
||||
return false;
|
||||
}
|
||||
if (key.startsWith("view:")) {
|
||||
return false;
|
||||
}
|
||||
return PluginSessionKeys.ROOM.equals(key) || key.startsWith("peer:");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,591 @@
|
||||
package pitiupi.screenCast.GUI;
|
||||
|
||||
import pitiupi.application.messaging.MessageDispatcher;
|
||||
import pitiupi.application.messaging.PluginTransportCatalog;
|
||||
import pitiupi.application.messaging.SendTarget;
|
||||
import pitiupi.application.messaging.TransportMode;
|
||||
import pitiupi.application.presence.UserPeerView;
|
||||
import pitiupi.control.ConnectionManager;
|
||||
import pitiupi.presentation.shell.PeerDraftPanel;
|
||||
import pitiupi.presentation.shell.PluginRoutingPanel;
|
||||
import pitiupi.presentation.shell.PluginSessionKeys;
|
||||
import pitiupi.presentation.shell.PluginSessionWorkspace;
|
||||
import pitiupi.presentation.shell.SessionPeerLabels;
|
||||
import pitiupi.presentation.shell.SessionTabBar;
|
||||
import pitiupi.presentation.shell.ui.ShellUi;
|
||||
|
||||
import pitiupi.screenCast.control.AlarmClock;
|
||||
import pitiupi.screenCast.control.ImageTransformation;
|
||||
import pitiupi.screenCast.control.ScreenCaptureDiagnostics;
|
||||
import pitiupi.screenCast.control.ScreenShot;
|
||||
import pitiupi.screenCast.net.ScreenCastMessage;
|
||||
|
||||
import java.awt.AWTException;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.CardLayout;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.event.HierarchyEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JToggleButton;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
/**
|
||||
* Painel de compartilhamento de tela.
|
||||
* Backlog UX: prévia dividida (recebendo + enviando) na mesma aba peer; multi-share simultâneo.
|
||||
*/
|
||||
public class ScreenCastPanel extends JPanel {
|
||||
|
||||
public static final int TIME = 1000;
|
||||
private static final int MAX_WIDTH = 800;
|
||||
private static final int MAX_HEIGHT = 600;
|
||||
private static final String CARD_DRAFT = "__draft__";
|
||||
private static final String IDLE_ROOM_HTML =
|
||||
"<html><p>Ative <b>Compartilhamento da sua tela</b> para enviar para todos na rede (multicast).</p></html>";
|
||||
private static final String IDLE_PEER_HTML =
|
||||
"<html><p>Ative o compartilhamento para enviar só para este peer (unicast).</p></html>";
|
||||
|
||||
private AlarmClock alarmClock;
|
||||
private Timer timer;
|
||||
private float compressRatio;
|
||||
private final ConnectionManager connectionManager;
|
||||
private final PluginSessionWorkspace workspace;
|
||||
private final PluginRoutingPanel routingPanel;
|
||||
private final SessionTabBar sessionTabs;
|
||||
private final JToggleButton shareToggle = new JToggleButton("Compartilhamento da sua tela (desligado)");
|
||||
private final Map<String, Boolean> sharingByTabKey = new HashMap<>();
|
||||
private boolean suppressShareToggleEvents;
|
||||
private String shareContextTabKey;
|
||||
private String lastSessionKey = PluginSessionKeys.ROOM;
|
||||
private final Map<String, String> peerIpsByViewKey = new HashMap<>();
|
||||
private final Map<String, String> peerDisplayNames = new HashMap<>();
|
||||
private final Map<String, ScreenCastReceiveScope> receiveScopes = new HashMap<>();
|
||||
private final CardLayout bodyLayout = new CardLayout();
|
||||
private final JPanel bodyCards = new JPanel(bodyLayout);
|
||||
private final Map<String, JPanel> bodyPanels = new HashMap<>();
|
||||
private PeerDraftPanel draftPanel;
|
||||
|
||||
public ScreenCastPanel(ConnectionManager connectionManager) {
|
||||
this.connectionManager = connectionManager;
|
||||
this.compressRatio = 0.55f;
|
||||
this.routingPanel = new PluginRoutingPanel(
|
||||
PluginTransportCatalog.PluginKind.SCREEN_CAST,
|
||||
"<html><i>Receba pela lista lateral (notificação) ou use + Nova para um IP. "
|
||||
+ "A aba Todos envia em multicast; abas de peer usam unicast.</i></html>",
|
||||
false
|
||||
);
|
||||
this.workspace = new PluginSessionWorkspace(routingPanel, buildShareToolbar());
|
||||
this.sessionTabs = workspace.getSessionTabBar();
|
||||
sessionTabs.setCloseFallbackKey(PluginSessionKeys.ROOM);
|
||||
initComponents();
|
||||
}
|
||||
|
||||
private JPanel buildShareToolbar() {
|
||||
shareToggle.setFocusPainted(false);
|
||||
shareToggle.addActionListener(e -> {
|
||||
if (suppressShareToggleEvents) {
|
||||
return;
|
||||
}
|
||||
String key = sessionTabs.selectedKey();
|
||||
if (!ScreenCastOutboundTabs.isOutboundShareTabKey(key)) {
|
||||
return;
|
||||
}
|
||||
sharingByTabKey.put(key, shareToggle.isSelected());
|
||||
updateShareToggleLabel();
|
||||
applySharingForActiveTab();
|
||||
});
|
||||
JPanel shareRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 4));
|
||||
shareRow.add(shareToggle);
|
||||
return shareRow;
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
setLayout(new BorderLayout());
|
||||
draftPanel = new PeerDraftPanel(
|
||||
"Nova sessão por IP",
|
||||
"Informe o IP para enviar ou receber unicast. O nome na aba atualiza quando o peer for conhecido.",
|
||||
this::commitDraftIp
|
||||
);
|
||||
bodyCards.add(draftPanel, CARD_DRAFT);
|
||||
workspace.getBodyHost().add(bodyCards, BorderLayout.CENTER);
|
||||
add(workspace, BorderLayout.CENTER);
|
||||
timer = new Timer();
|
||||
addHierarchyListener(e -> {
|
||||
if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 && isShowing()) {
|
||||
onSessionTabSelected(sessionTabs.selectedKey());
|
||||
}
|
||||
});
|
||||
initSessionTabs();
|
||||
}
|
||||
|
||||
private void initSessionTabs() {
|
||||
sessionTabs.addOrSelectTab(
|
||||
PluginSessionKeys.ROOM,
|
||||
"Todos",
|
||||
"Transmissão para todos na rede (multicast)",
|
||||
new JPanel(),
|
||||
false
|
||||
);
|
||||
sessionTabs.setOnTabSelected(this::onSessionTabSelected);
|
||||
sessionTabs.setOnAddPeerRequested(this::openDraftPeerTab);
|
||||
sessionTabs.setOnTabClosed(this::onSessionTabClosed);
|
||||
ensureBodyPanel(PluginSessionKeys.ROOM);
|
||||
setBodyMessage(PluginSessionKeys.ROOM, ShellUi.mutedLabel(IDLE_ROOM_HTML));
|
||||
sessionTabs.selectTab(PluginSessionKeys.ROOM);
|
||||
}
|
||||
|
||||
private void onSessionTabClosed(String key) {
|
||||
if (PluginSessionKeys.PEER_DRAFT.equals(key)) {
|
||||
if (draftPanel != null) {
|
||||
draftPanel.clearIpField();
|
||||
}
|
||||
SwingUtilities.invokeLater(this::restoreSessionAfterDraftClosed);
|
||||
}
|
||||
sharingByTabKey.remove(key);
|
||||
}
|
||||
|
||||
private void restoreSessionAfterDraftClosed() {
|
||||
String restore = lastSessionKey != null && sessionTabs.hasTab(lastSessionKey)
|
||||
? lastSessionKey
|
||||
: PluginSessionKeys.ROOM;
|
||||
if (PluginSessionKeys.PEER_DRAFT.equals(restore)) {
|
||||
restore = PluginSessionKeys.ROOM;
|
||||
}
|
||||
sessionTabs.selectTabSilently(restore);
|
||||
onSessionTabSelected(restore);
|
||||
}
|
||||
|
||||
private void onSessionTabSelected(String key) {
|
||||
persistShareStateForContextTab();
|
||||
if (key != null && !PluginSessionKeys.PEER_DRAFT.equals(key)) {
|
||||
lastSessionKey = key;
|
||||
}
|
||||
if (PluginSessionKeys.PEER_DRAFT.equals(key)) {
|
||||
cancelShareTimer();
|
||||
applyShareToggleForTab(key);
|
||||
showBodyCard(CARD_DRAFT);
|
||||
draftPanel.focusIpField();
|
||||
return;
|
||||
}
|
||||
if (key == null) {
|
||||
key = PluginSessionKeys.ROOM;
|
||||
}
|
||||
showBodyCard(key);
|
||||
applyRoutingForSessionKey(key);
|
||||
applyShareToggleForTab(key);
|
||||
applySharingForActiveTab();
|
||||
if (!isSharingForTab(key)) {
|
||||
refreshOutboundBodyForTab(key);
|
||||
}
|
||||
}
|
||||
|
||||
private void persistShareStateForContextTab() {
|
||||
if (shareContextTabKey != null && ScreenCastOutboundTabs.isOutboundShareTabKey(shareContextTabKey)) {
|
||||
sharingByTabKey.put(shareContextTabKey, shareToggle.isSelected());
|
||||
}
|
||||
}
|
||||
|
||||
private void applyShareToggleForTab(String key) {
|
||||
shareContextTabKey = ScreenCastOutboundTabs.isOutboundShareTabKey(key) ? key : null;
|
||||
suppressShareToggleEvents = true;
|
||||
try {
|
||||
if (shareContextTabKey == null) {
|
||||
shareToggle.setEnabled(false);
|
||||
shareToggle.setSelected(false);
|
||||
} else {
|
||||
shareToggle.setEnabled(true);
|
||||
shareToggle.setSelected(sharingByTabKey.getOrDefault(shareContextTabKey, false));
|
||||
}
|
||||
updateShareToggleLabel();
|
||||
} finally {
|
||||
suppressShareToggleEvents = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void setShareToggleSelectedSilently(boolean selected) {
|
||||
suppressShareToggleEvents = true;
|
||||
try {
|
||||
shareToggle.setSelected(selected);
|
||||
updateShareToggleLabel();
|
||||
} finally {
|
||||
suppressShareToggleEvents = false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSharingForTab(String key) {
|
||||
return ScreenCastOutboundTabs.isOutboundShareTabKey(key)
|
||||
&& sharingByTabKey.getOrDefault(key, false);
|
||||
}
|
||||
|
||||
private void applySharingForActiveTab() {
|
||||
cancelShareTimer();
|
||||
String key = sessionTabs.selectedKey();
|
||||
if (!ScreenCastOutboundTabs.isOutboundShareTabKey(key)) {
|
||||
return;
|
||||
}
|
||||
boolean on = sharingByTabKey.getOrDefault(key, shareToggle.isSelected());
|
||||
sharingByTabKey.put(key, on);
|
||||
if (!on) {
|
||||
refreshOutboundBodyForTab(key);
|
||||
return;
|
||||
}
|
||||
syncRoutingFromActiveSessionTab();
|
||||
timer = new Timer();
|
||||
alarmClock = new AlarmClock(this);
|
||||
timer.scheduleAtFixedRate(alarmClock, 0, TIME);
|
||||
String previewKey = previewTabKey();
|
||||
ensureBodyPanel(previewKey);
|
||||
setBodyMessage(previewKey, ShellUi.mutedLabel("Capturando tela…"));
|
||||
showBodyCard(previewKey);
|
||||
}
|
||||
|
||||
private void refreshOutboundBodyForTab(String key) {
|
||||
if (key == null || PluginSessionKeys.PEER_DRAFT.equals(key) || key.startsWith("view:")) {
|
||||
return;
|
||||
}
|
||||
if (isSharingForTab(key)) {
|
||||
return;
|
||||
}
|
||||
if (PluginSessionKeys.ROOM.equals(key)) {
|
||||
setBodyMessage(key, ShellUi.mutedLabel(IDLE_ROOM_HTML));
|
||||
} else if (key.startsWith("peer:")) {
|
||||
setBodyMessage(key, ShellUi.mutedLabel(IDLE_PEER_HTML));
|
||||
}
|
||||
}
|
||||
|
||||
private void syncRoutingFromActiveSessionTab() {
|
||||
applyRoutingForSessionKey(sessionTabs.selectedKey());
|
||||
}
|
||||
|
||||
private void applyRoutingForSessionKey(String key) {
|
||||
if (key == null) {
|
||||
routingPanel.bindRoomSession();
|
||||
routingPanel.setReceiveScopeHint(null);
|
||||
return;
|
||||
}
|
||||
if (PluginSessionKeys.PEER_DRAFT.equals(key)) {
|
||||
return;
|
||||
}
|
||||
if (PluginSessionKeys.ROOM.equals(key)) {
|
||||
routingPanel.bindRoomSession();
|
||||
routingPanel.setReceiveScopeHint(null);
|
||||
return;
|
||||
}
|
||||
if (key.startsWith("peer:")) {
|
||||
String ip = key.substring("peer:".length());
|
||||
String name = peerDisplayNames.getOrDefault(ip, ip);
|
||||
routingPanel.bindPeerSession(ip, name);
|
||||
routingPanel.setReceiveScopeHint(null);
|
||||
return;
|
||||
}
|
||||
if (key.startsWith("view:")) {
|
||||
String ip = peerIpsByViewKey.get(key);
|
||||
String title = key.substring("view:".length());
|
||||
ScreenCastReceiveScope scope = receiveScopes.getOrDefault(key, ScreenCastReceiveScope.BROADCAST);
|
||||
if (ip != null && !ip.isBlank()) {
|
||||
routingPanel.bindPeerSession(ip, title, scope.bannerHtml());
|
||||
} else {
|
||||
routingPanel.bindPeerSession("", title, scope.bannerHtml());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void openDraftPeerTab() {
|
||||
String previous = sessionTabs.selectedKey();
|
||||
if (previous != null && !PluginSessionKeys.PEER_DRAFT.equals(previous)) {
|
||||
lastSessionKey = previous;
|
||||
}
|
||||
if (!sessionTabs.hasTab(PluginSessionKeys.PEER_DRAFT)) {
|
||||
sessionTabs.addOrSelectTab(
|
||||
PluginSessionKeys.PEER_DRAFT,
|
||||
"Nova conversa",
|
||||
"Informe o IP do peer",
|
||||
new JPanel(),
|
||||
true
|
||||
);
|
||||
} else {
|
||||
sessionTabs.selectTab(PluginSessionKeys.PEER_DRAFT);
|
||||
}
|
||||
showBodyCard(CARD_DRAFT);
|
||||
draftPanel.focusIpField();
|
||||
}
|
||||
|
||||
private void commitDraftIp(String ip) {
|
||||
if (draftPanel != null) {
|
||||
draftPanel.clearIpField();
|
||||
}
|
||||
if (sessionTabs.hasTab(PluginSessionKeys.PEER_DRAFT)) {
|
||||
sessionTabs.closeTab(PluginSessionKeys.PEER_DRAFT);
|
||||
}
|
||||
openPeerTab(ip, ip);
|
||||
}
|
||||
|
||||
private void openPeerTab(String ip, String displayName) {
|
||||
String canonical = canonicalPeerIp(ip);
|
||||
String resolved = SessionPeerLabels.resolveDisplayName(connectionManager, canonical, displayName);
|
||||
String key = PluginSessionKeys.peer(canonical);
|
||||
peerDisplayNames.put(canonical, resolved);
|
||||
ensureBodyPanel(key);
|
||||
setBodyMessage(key, ShellUi.mutedLabel(IDLE_PEER_HTML));
|
||||
if (sessionTabs.hasTab(key)) {
|
||||
sessionTabs.setTabTitle(key, resolved, canonical);
|
||||
sessionTabs.selectTab(key);
|
||||
} else {
|
||||
sessionTabs.addOrSelectTab(key, resolved, canonical, new JPanel(), true);
|
||||
}
|
||||
routingPanel.bindPeerSession(canonical, resolved);
|
||||
showBodyCard(key);
|
||||
}
|
||||
|
||||
private static String canonicalPeerIp(String ip) {
|
||||
if (ip == null || ip.isBlank()) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
return InetAddress.getByName(ip.trim()).getHostAddress();
|
||||
} catch (Exception ex) {
|
||||
return ip.trim();
|
||||
}
|
||||
}
|
||||
|
||||
public void focusMulticastSharing() {
|
||||
ensureTodosTab();
|
||||
sharingByTabKey.put(PluginSessionKeys.ROOM, true);
|
||||
sessionTabs.selectTab(PluginSessionKeys.ROOM);
|
||||
routingPanel.bindRoomSession();
|
||||
routingPanel.selectTransport(TransportMode.MULTICAST_UDP);
|
||||
setShareToggleSelectedSilently(true);
|
||||
applySharingForActiveTab();
|
||||
}
|
||||
|
||||
public void focusPeerSharing(UserPeerView peer) {
|
||||
if (peer == null || peer.isBroadcastTarget() || peer.isSelf()) {
|
||||
return;
|
||||
}
|
||||
peer.getInetAddress().ifPresent(addr -> {
|
||||
String ip = addr.getHostAddress();
|
||||
openPeerTab(ip, peer.getDisplayName());
|
||||
String key = PluginSessionKeys.peer(canonicalPeerIp(ip));
|
||||
sharingByTabKey.put(key, true);
|
||||
routingPanel.selectTransport(TransportMode.UNICAST_UDP);
|
||||
setShareToggleSelectedSilently(true);
|
||||
applySharingForActiveTab();
|
||||
});
|
||||
}
|
||||
|
||||
public void focusPeerAddress(UserPeerView peer) {
|
||||
if (peer == null || peer.isBroadcastTarget() || peer.isSelf()) {
|
||||
return;
|
||||
}
|
||||
peer.getInetAddress().ifPresent(addr -> openPeerTab(addr.getHostAddress(), peer.getDisplayName()));
|
||||
}
|
||||
|
||||
private void ensureTodosTab() {
|
||||
if (!sessionTabs.hasTab(PluginSessionKeys.ROOM)) {
|
||||
sessionTabs.addOrSelectTab(
|
||||
PluginSessionKeys.ROOM,
|
||||
"Todos",
|
||||
"Transmissão para todos na rede (multicast)",
|
||||
new JPanel(),
|
||||
false
|
||||
);
|
||||
ensureBodyPanel(PluginSessionKeys.ROOM);
|
||||
setBodyMessage(PluginSessionKeys.ROOM, ShellUi.mutedLabel(IDLE_ROOM_HTML));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateShareToggleLabel() {
|
||||
shareToggle.setText(shareToggle.isSelected()
|
||||
? "Compartilhamento da sua tela (ligado)"
|
||||
: "Compartilhamento da sua tela (desligado)");
|
||||
}
|
||||
|
||||
private void cancelShareTimer() {
|
||||
if (timer != null) {
|
||||
timer.cancel();
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void stopSharing() {
|
||||
sharingByTabKey.replaceAll((k, v) -> false);
|
||||
setShareToggleSelectedSilently(false);
|
||||
cancelShareTimer();
|
||||
refreshOutboundBodyForTab(sessionTabs.selectedKey());
|
||||
}
|
||||
|
||||
public void sendScreenCastMessage() {
|
||||
String activeKey = sessionTabs.selectedKey();
|
||||
if (!isSharingForTab(activeKey)) {
|
||||
return;
|
||||
}
|
||||
syncRoutingFromActiveSessionTab();
|
||||
try {
|
||||
BufferedImage image = ScreenShot.takeAShot();
|
||||
if (image == null || image.getWidth() < 1 || image.getHeight() < 1) {
|
||||
Logger.getLogger(ScreenCastPanel.class.getName()).log(Level.WARNING, "Captura de tela vazia");
|
||||
showCaptureWarning();
|
||||
return;
|
||||
}
|
||||
boolean black = ScreenCaptureDiagnostics.isLikelyBlackFrame(image);
|
||||
BufferedImage rescaled = ImageTransformation.resize(image, MAX_WIDTH, MAX_HEIGHT);
|
||||
byte[] byteImage = ImageTransformation.compress(rescaled, compressRatio);
|
||||
if (byteImage == null || byteImage.length < 32) {
|
||||
return;
|
||||
}
|
||||
ScreenCastMessage message = new ScreenCastMessage(connectionManager.getUserName(), byteImage);
|
||||
|
||||
PluginRoutingPanel.RoutingSnapshot routing = routingPanel.snapshot();
|
||||
TransportMode mode = routing.transport();
|
||||
SendTarget target = resolveSendTarget(routing);
|
||||
if (target == null || mode == null) {
|
||||
return;
|
||||
}
|
||||
int sent = MessageDispatcher.send(connectionManager, message, target, mode);
|
||||
if (sent == 0 && mode == TransportMode.MULTICAST_UDP) {
|
||||
compressRatio = Math.max(0.25f, compressRatio - 0.1f);
|
||||
}
|
||||
if (black) {
|
||||
showCaptureWarning();
|
||||
} else {
|
||||
showLocalPreview(rescaled);
|
||||
}
|
||||
} catch (AWTException | IOException ex) {
|
||||
Logger.getLogger(AlarmClock.class.getName()).log(Level.SEVERE, null, ex);
|
||||
showCaptureWarning();
|
||||
} catch (Exception ex) {
|
||||
Logger.getLogger(ScreenCastPanel.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void showCaptureWarning() {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
JLabel label = new JLabel(ScreenCaptureDiagnostics.blackFrameUserMessage());
|
||||
String key = previewTabKey();
|
||||
setBodyContent(key, label);
|
||||
showBodyCard(key);
|
||||
});
|
||||
}
|
||||
|
||||
private void showLocalPreview(BufferedImage image) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
String key = previewTabKey();
|
||||
JLabel label = new JLabel(new ImageIcon(image));
|
||||
setBodyContent(key, new JScrollPane(label));
|
||||
showBodyCard(key);
|
||||
});
|
||||
}
|
||||
|
||||
private SendTarget resolveSendTarget(PluginRoutingPanel.RoutingSnapshot routing) throws Exception {
|
||||
if (routing.everyoneInRoom()) {
|
||||
return SendTarget.broadcastAll();
|
||||
}
|
||||
String ip = routing.peerIp();
|
||||
if (ip == null || ip.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return SendTarget.singlePeer(InetAddress.getByName(ip));
|
||||
}
|
||||
|
||||
public void receiveScreenCastMessage(ScreenCastMessage message, ScreenCastReceiveScope scope) {
|
||||
if (isOwnOutboundFrame(message)) {
|
||||
return;
|
||||
}
|
||||
byte[] bytes = message.getImageByte();
|
||||
String viewKey = "view:" + message.getName();
|
||||
if (bytes == null || bytes.length < 32) {
|
||||
SwingUtilities.invokeLater(() -> updateTabError(viewKey, message.getName(), "Frame vazio ou corrompido.", scope));
|
||||
return;
|
||||
}
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
try {
|
||||
BufferedImage image = ImageIO.read(new ByteArrayInputStream(bytes));
|
||||
if (image == null) {
|
||||
updateTabError(viewKey, message.getName(), "Não foi possível decodificar a imagem.", scope);
|
||||
return;
|
||||
}
|
||||
receiveScopes.put(viewKey, scope != null ? scope : ScreenCastReceiveScope.BROADCAST);
|
||||
resolveSenderIp(message.getName(), viewKey);
|
||||
sessionTabs.addOrSelectTab(viewKey, message.getName(), message.getName(), new JPanel(), true);
|
||||
setBodyContent(viewKey, new JScrollPane(new JLabel(new ImageIcon(image))));
|
||||
sessionTabs.selectTab(viewKey);
|
||||
showBodyCard(viewKey);
|
||||
} catch (IOException ex) {
|
||||
updateTabError(viewKey, message.getName(), "Erro ao ler imagem: " + ex.getMessage(), scope);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void resolveSenderIp(String senderName, String viewKey) {
|
||||
for (UserPeerView view : connectionManager.getPresenceService().listPeerViews()) {
|
||||
if (view.isBroadcastTarget()) {
|
||||
continue;
|
||||
}
|
||||
if (senderName != null && senderName.equals(view.getDisplayName())) {
|
||||
view.getInetAddress().ifPresent(addr -> peerIpsByViewKey.put(viewKey, addr.getHostAddress()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTabError(String tabKey, String title, String error, ScreenCastReceiveScope scope) {
|
||||
receiveScopes.put(tabKey, scope != null ? scope : ScreenCastReceiveScope.BROADCAST);
|
||||
JLabel label = new JLabel("<html><p style='color:#c62828'>" + error + "</p></html>");
|
||||
setBodyContent(tabKey, label);
|
||||
sessionTabs.addOrSelectTab(tabKey, title, title, new JPanel(), true);
|
||||
showBodyCard(tabKey);
|
||||
}
|
||||
|
||||
private void ensureBodyPanel(String key) {
|
||||
if (!bodyPanels.containsKey(key)) {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
bodyPanels.put(key, panel);
|
||||
bodyCards.add(panel, key);
|
||||
}
|
||||
}
|
||||
|
||||
private void setBodyContent(String key, java.awt.Component content) {
|
||||
ensureBodyPanel(key);
|
||||
JPanel panel = bodyPanels.get(key);
|
||||
panel.removeAll();
|
||||
panel.add(content, BorderLayout.CENTER);
|
||||
panel.revalidate();
|
||||
panel.repaint();
|
||||
}
|
||||
|
||||
private void setBodyMessage(String key, JLabel label) {
|
||||
setBodyContent(key, label);
|
||||
}
|
||||
|
||||
private String previewTabKey() {
|
||||
String selected = sessionTabs.selectedKey();
|
||||
if (selected != null && !PluginSessionKeys.PEER_DRAFT.equals(selected)) {
|
||||
return selected;
|
||||
}
|
||||
return PluginSessionKeys.ROOM;
|
||||
}
|
||||
|
||||
private boolean isOwnOutboundFrame(ScreenCastMessage message) {
|
||||
if (message == null || message.getName() == null) {
|
||||
return false;
|
||||
}
|
||||
String localName = connectionManager.getUserName();
|
||||
return localName != null && localName.equals(message.getName().trim());
|
||||
}
|
||||
|
||||
private void showBodyCard(String card) {
|
||||
bodyLayout.show(bodyCards, card);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package pitiupi.screenCast.GUI;
|
||||
|
||||
public enum ScreenCastReceiveScope {
|
||||
BROADCAST,
|
||||
DIRECT;
|
||||
|
||||
public String bannerHtml() {
|
||||
if (this == BROADCAST) {
|
||||
return "<html><i>Transmissão para todos na rede</i></html>";
|
||||
}
|
||||
return "<html><i>Transmissão direcionada a você</i></html>";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package pitiupi.screenCast.control;
|
||||
|
||||
import java.util.TimerTask;
|
||||
import pitiupi.screenCast.GUI.ScreenCastPanel;
|
||||
|
||||
public class AlarmClock extends TimerTask {
|
||||
|
||||
private final ScreenCastPanel screenCastPanel;
|
||||
|
||||
public AlarmClock(ScreenCastPanel screenCastPanel) {
|
||||
this.screenCastPanel = screenCastPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
screenCastPanel.sendScreenCastMessage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package pitiupi.screenCast.control;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author flavio
|
||||
*/
|
||||
public class ImageTransformation {
|
||||
|
||||
public static byte[] toByteArray(BufferedImage image) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ImageIO.write(image, "jpg", baos);
|
||||
byte[] data = baos.toByteArray();
|
||||
return data;
|
||||
}
|
||||
|
||||
public static byte[] compress(BufferedImage image, float scale) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
|
||||
ImageWriter writer = writers.next();
|
||||
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||
param.setCompressionQuality(scale);
|
||||
ImageOutputStream ios = ImageIO.createImageOutputStream(baos);
|
||||
writer.setOutput(ios);
|
||||
writer.write(null, new IIOImage(image, null, null), param);
|
||||
byte[] data = baos.toByteArray();
|
||||
writer.dispose();
|
||||
return data;
|
||||
}
|
||||
|
||||
public static BufferedImage resize(BufferedImage img, int maxWidth, int maxHeight) throws IOException {
|
||||
int scaledWidth = maxWidth;
|
||||
int scaledHeight = (int) (img.getHeight() * ((double) scaledWidth / img.getWidth()));
|
||||
|
||||
if (scaledHeight > maxHeight) {
|
||||
scaledHeight = maxHeight;
|
||||
scaledWidth = (int) (img.getWidth() * ((double) scaledHeight / img.getHeight()));
|
||||
|
||||
if (scaledWidth > maxWidth) {
|
||||
scaledWidth = maxWidth;
|
||||
scaledHeight = maxHeight;
|
||||
}
|
||||
}
|
||||
|
||||
Image resized = img.getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH);
|
||||
BufferedImage buffered = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D graphics = buffered.createGraphics();
|
||||
graphics.drawImage(resized, 0, 0, null);
|
||||
graphics.dispose();
|
||||
return buffered;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package pitiupi.screenCast.control;
|
||||
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public final class ScreenCaptureDiagnostics {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(ScreenCaptureDiagnostics.class.getName());
|
||||
private static boolean environmentLogged;
|
||||
|
||||
private ScreenCaptureDiagnostics() {
|
||||
}
|
||||
|
||||
public static boolean isLikelyBlackFrame(BufferedImage image) {
|
||||
if (image == null || image.getWidth() < 8 || image.getHeight() < 8) {
|
||||
return true;
|
||||
}
|
||||
int stepX = Math.max(1, image.getWidth() / 16);
|
||||
int stepY = Math.max(1, image.getHeight() / 16);
|
||||
int dark = 0;
|
||||
int samples = 0;
|
||||
for (int y = 0; y < image.getHeight(); y += stepY) {
|
||||
for (int x = 0; x < image.getWidth(); x += stepX) {
|
||||
int rgb = image.getRGB(x, y);
|
||||
int r = (rgb >> 16) & 0xff;
|
||||
int g = (rgb >> 8) & 0xff;
|
||||
int b = rgb & 0xff;
|
||||
if (r < 12 && g < 12 && b < 12) {
|
||||
dark++;
|
||||
}
|
||||
samples++;
|
||||
}
|
||||
}
|
||||
return samples > 0 && dark >= samples * 0.92;
|
||||
}
|
||||
|
||||
public static String blackFrameUserMessage() {
|
||||
logEnvironmentOnce();
|
||||
String wayland = System.getenv("WAYLAND_DISPLAY");
|
||||
String display = System.getenv("DISPLAY");
|
||||
return "<html><p><b>Captura retornou tela preta.</b></p>"
|
||||
+ "<p>Em WSL/WSLg o <code>java.awt.Robot</code> costuma não capturar o desktop.</p>"
|
||||
+ "<ul>"
|
||||
+ "<li>Teste o Pitiupi no <b>Windows nativo</b> (fora do WSL) para compartilhar a tela real.</li>"
|
||||
+ "<li>No WSL, confira WSLg: <code>WAYLAND_DISPLAY="
|
||||
+ (wayland != null ? wayland : "—")
|
||||
+ "</code>, <code>DISPLAY="
|
||||
+ (display != null ? display : "—")
|
||||
+ "</code>.</li>"
|
||||
+ "<li>Se os frames ainda saem pretos, o envio na rede pode ocorrer, mas o preview local não reflete sua tela.</li>"
|
||||
+ "</ul></html>";
|
||||
}
|
||||
|
||||
private static void logEnvironmentOnce() {
|
||||
if (environmentLogged) {
|
||||
return;
|
||||
}
|
||||
environmentLogged = true;
|
||||
try {
|
||||
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
LOG.log(Level.INFO, "Screen capture env: {0}, displays={1}, DISPLAY={2}, WAYLAND_DISPLAY={3}",
|
||||
new Object[]{
|
||||
env.getClass().getSimpleName(),
|
||||
env.getScreenDevices().length,
|
||||
System.getenv("DISPLAY"),
|
||||
System.getenv("WAYLAND_DISPLAY")
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
LOG.log(Level.WARNING, "Screen capture env unavailable", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package pitiupi.screenCast.control;
|
||||
|
||||
import java.awt.AWTException;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Robot;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author flavio
|
||||
*/
|
||||
public class ScreenShot {
|
||||
|
||||
public static BufferedImage takeAShot() throws AWTException, IOException {
|
||||
Robot robot = new Robot();
|
||||
Rectangle screenRect = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
|
||||
BufferedImage output = robot.createScreenCapture(screenRect);
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package pitiupi.screenCast.net;
|
||||
|
||||
import pitiupi.models.Messages.Message;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author flavio
|
||||
*/
|
||||
public class ScreenCastMessage extends Message {
|
||||
|
||||
private String name;
|
||||
private byte[] imageByte;
|
||||
|
||||
public ScreenCastMessage() {
|
||||
}
|
||||
|
||||
public ScreenCastMessage(String name, byte[] imageByte) {
|
||||
this.name = name;
|
||||
this.imageByte = imageByte;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name the name to set
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the imageByte
|
||||
*/
|
||||
public byte[] getImageByte() {
|
||||
return imageByte;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param imageByte the imageByte to set
|
||||
*/
|
||||
public void setImageByte(byte[] imageByte) {
|
||||
this.imageByte = imageByte;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package pitiupi.screenCast.plugin;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import pitiupi.api.plugin.PeerActionsRegistrar;
|
||||
import pitiupi.api.plugin.PluginWorkspace;
|
||||
import pitiupi.application.activity.PeerActivityInbox;
|
||||
import pitiupi.application.peeractions.PeerAction;
|
||||
import pitiupi.application.presence.UserPeerView;
|
||||
import pitiupi.application.peeractions.PeerActionTarget;
|
||||
import pitiupi.application.peeractions.PeerTarget;
|
||||
import pitiupi.control.ConnectionManager;
|
||||
import pitiupi.control.GuiManager;
|
||||
import pitiupi.models.Menus.MenuCategory;
|
||||
import pitiupi.models.Messages.Message;
|
||||
import pitiupi.models.Plugin;
|
||||
import pitiupi.screenCast.GUI.ScreenCastMenu;
|
||||
import pitiupi.screenCast.GUI.ScreenCastPanel;
|
||||
import pitiupi.screenCast.GUI.ScreenCastReceiveScope;
|
||||
import pitiupi.screenCast.net.ScreenCastMessage;
|
||||
|
||||
public class ScreenCastPlugin extends Plugin {
|
||||
|
||||
private ScreenCastPanel screenCastPanel;
|
||||
private PluginWorkspace workspace;
|
||||
private ConnectionManager connectionManager;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ScreenCast";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Compartilhar tela";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthor() {
|
||||
return "Flávio Schiavoni";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "0.1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return "Pitiupi.ScreenCast.Example";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createPlugin(ConnectionManager connectionManager, PluginWorkspace workspace) {
|
||||
this.connectionManager = connectionManager;
|
||||
this.workspace = workspace;
|
||||
this.screenCastPanel = new ScreenCastPanel(connectionManager);
|
||||
bindShellWorkspaceContent(screenCastPanel);
|
||||
installPeerActions(workspace.peerActions());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createPlugin(ConnectionManager connectionManager, GuiManager guiManager) {
|
||||
this.screenCastPanel = new ScreenCastPanel(connectionManager);
|
||||
guiManager.createMenu(MenuCategory.Streaming, new ScreenCastMenu(screenCastPanel));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShellTabClosed() {
|
||||
if (screenCastPanel != null) {
|
||||
screenCastPanel.stopSharing();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installPeerActions(PeerActionsRegistrar registrar) {
|
||||
registrar.register(PeerAction.of(
|
||||
"screencast.multicast",
|
||||
"Compartilhar na rede (multicast)",
|
||||
PeerActionTarget.ALL_PEERS,
|
||||
target -> {
|
||||
workspace.openOrFocusTab(getIdentifier(), getDisplayName());
|
||||
screenCastPanel.focusMulticastSharing();
|
||||
}
|
||||
));
|
||||
registrar.register(PeerAction.of(
|
||||
"screencast.peer",
|
||||
"Compartilhar com este peer",
|
||||
PeerActionTarget.SINGLE_PEER,
|
||||
target -> {
|
||||
workspace.openOrFocusTab(getIdentifier(), getDisplayName());
|
||||
if (target instanceof PeerTarget.Single single) {
|
||||
screenCastPanel.focusPeerSharing(single.peer());
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Class<? extends Message>> getMessages() {
|
||||
return List.of(ScreenCastMessage.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveMessage(Message message) {
|
||||
if (message instanceof ScreenCastMessage msg && screenCastPanel != null) {
|
||||
if (isOwnOutboundFrame(msg)) {
|
||||
return;
|
||||
}
|
||||
ScreenCastReceiveScope scope = classifyIncoming(msg);
|
||||
recordScreenCastActivity(msg, scope);
|
||||
screenCastPanel.receiveScreenCastMessage(msg, scope);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOwnOutboundFrame(ScreenCastMessage msg) {
|
||||
if (connectionManager == null || msg.getName() == null) {
|
||||
return false;
|
||||
}
|
||||
String localName = connectionManager.getUserName();
|
||||
return localName != null && localName.equals(msg.getName().trim());
|
||||
}
|
||||
|
||||
private ScreenCastReceiveScope classifyIncoming(ScreenCastMessage msg) {
|
||||
if (connectionManager == null || msg.getName() == null) {
|
||||
return ScreenCastReceiveScope.BROADCAST;
|
||||
}
|
||||
for (UserPeerView view : connectionManager.getPresenceService().listPeerViews()) {
|
||||
if (!view.isBroadcastTarget()
|
||||
&& msg.getName().equals(view.getDisplayName())
|
||||
&& view.getInstanceId() != null) {
|
||||
return ScreenCastReceiveScope.DIRECT;
|
||||
}
|
||||
}
|
||||
return ScreenCastReceiveScope.BROADCAST;
|
||||
}
|
||||
|
||||
private void recordScreenCastActivity(ScreenCastMessage msg, ScreenCastReceiveScope scope) {
|
||||
if (connectionManager == null) {
|
||||
return;
|
||||
}
|
||||
PeerActivityInbox inbox = connectionManager.getPeerActivityInbox();
|
||||
if (scope == ScreenCastReceiveScope.DIRECT) {
|
||||
for (UserPeerView view : connectionManager.getPresenceService().listPeerViews()) {
|
||||
if (!view.isBroadcastTarget()
|
||||
&& msg.getName() != null
|
||||
&& msg.getName().equals(view.getDisplayName())
|
||||
&& view.getInstanceId() != null) {
|
||||
inbox.record(view.getInstanceId(), getIdentifier(), "screencast.peer");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
inbox.recordBroadcast(getIdentifier(), "screencast.multicast");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user