mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-08-30 11:43:36 +02:00
misc: change the way of manipulating window's display screen
- avoid using screen number as display number on Linux - support for area selection over multiple screens on Linux - make getFrame null safe - make video-full-screen mode show in the correct screen - add the option of "share all screens" - use x11 api for unix system for sharing screen areas Gitlab: #160 Change-Id: Ibe47a4150b6a213950a0533d85e8cd7d5d159482
This commit is contained in:
parent
768ea9d601
commit
7f7e4b2202
21 changed files with 630 additions and 256 deletions
|
@ -103,10 +103,17 @@ unix {
|
|||
|
||||
LIBS += -L$${LRC}/lib -lringclient
|
||||
LIBS += -lqrencode
|
||||
LIBS += -lX11
|
||||
|
||||
isEmpty(PREFIX) { PREFIX = /tmp/$${TARGET}/bin }
|
||||
target.path = $$PREFIX/bin
|
||||
INSTALLS += target
|
||||
|
||||
# unix specific
|
||||
HEADERS += \
|
||||
src/xrectsel.h
|
||||
SOURCES += \
|
||||
src/xrectsel.c
|
||||
}
|
||||
|
||||
# Input
|
||||
|
|
|
@ -169,9 +169,12 @@ ApplicationWindow {
|
|||
|
||||
onClosing: root.close()
|
||||
|
||||
onScreenChanged: JamiQmlUtils.mainApplicationScreen = root.screen
|
||||
|
||||
Component.onCompleted: {
|
||||
if(!startAccountMigration()){
|
||||
startClient()
|
||||
}
|
||||
JamiQmlUtils.mainApplicationScreen = root.screen
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,8 +23,13 @@
|
|||
#include "lrcinstance.h"
|
||||
#include "qtutils.h"
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include "xrectsel.h"
|
||||
#endif
|
||||
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QApplication>
|
||||
#include <QPainter>
|
||||
#include <QScreen>
|
||||
|
||||
AvAdapter::AvAdapter(QObject* parent)
|
||||
|
@ -66,37 +71,62 @@ AvAdapter::populateVideoDeviceContextMenuItem()
|
|||
void
|
||||
AvAdapter::onVideoContextMenuDeviceItemClicked(const QString& deviceName)
|
||||
{
|
||||
auto* convModel = LRCInstance::getCurrentConversationModel();
|
||||
const auto conversation = convModel->getConversationForUID(LRCInstance::getCurrentConvUid());
|
||||
auto call = LRCInstance::getCallInfoForConversation(conversation);
|
||||
if (!call)
|
||||
return;
|
||||
|
||||
auto deviceId = LRCInstance::avModel().getDeviceIdFromName(deviceName);
|
||||
if (deviceId.isEmpty()) {
|
||||
qWarning() << "Couldn't find device: " << deviceName;
|
||||
return;
|
||||
}
|
||||
LRCInstance::avModel().setCurrentVideoCaptureDevice(deviceId);
|
||||
LRCInstance::avModel().switchInputTo(deviceId, call->id);
|
||||
LRCInstance::avModel().switchInputTo(deviceId, getCurrentCallId());
|
||||
}
|
||||
|
||||
void
|
||||
AvAdapter::shareEntireScreen(int screenNumber)
|
||||
{
|
||||
QScreen* screen = qApp->screens().at(screenNumber);
|
||||
QScreen* screen = QGuiApplication::screens().at(screenNumber);
|
||||
if (!screen)
|
||||
return;
|
||||
QRect rect = screen ? screen->geometry() : qApp->primaryScreen()->geometry();
|
||||
LRCInstance::avModel().setDisplay(screenNumber, rect.x(), rect.y(), rect.width(), rect.height());
|
||||
QRect rect = screen->geometry();
|
||||
|
||||
int display = 0;
|
||||
#ifdef Q_OS_WIN
|
||||
display = screenNumber;
|
||||
#else
|
||||
QString display_env {getenv("DISPLAY")};
|
||||
if (!display_env.isEmpty()) {
|
||||
auto list = display_env.split(":", Qt::SkipEmptyParts);
|
||||
// Should only be one display, so get the first one
|
||||
if (list.size() > 0) {
|
||||
display = list.at(0).toInt();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
LRCInstance::avModel()
|
||||
.setDisplay(display, rect.x(), rect.y(), rect.width(), rect.height(), getCurrentCallId());
|
||||
}
|
||||
|
||||
const QString
|
||||
void
|
||||
AvAdapter::shareAllScreens()
|
||||
{
|
||||
auto screens = QGuiApplication::screens();
|
||||
|
||||
int width = 0, height = 0;
|
||||
for (auto scr : screens) {
|
||||
width += scr->geometry().width();
|
||||
if (height < scr->geometry().height())
|
||||
height = scr->geometry().height();
|
||||
}
|
||||
|
||||
LRCInstance::avModel().setDisplay(0, 0, 0, width, height, getCurrentCallId());
|
||||
}
|
||||
|
||||
void
|
||||
AvAdapter::captureScreen(int screenNumber)
|
||||
{
|
||||
QScreen* screen = qApp->screens().at(screenNumber);
|
||||
QtConcurrent::run([this, screenNumber]() {
|
||||
QScreen* screen = QGuiApplication::screens().at(screenNumber);
|
||||
if (!screen)
|
||||
return QString("");
|
||||
return;
|
||||
/*
|
||||
* The screen window id is always 0.
|
||||
*/
|
||||
|
@ -105,32 +135,88 @@ AvAdapter::captureScreen(int screenNumber)
|
|||
QBuffer buffer;
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
pixmap.save(&buffer, "PNG");
|
||||
return Utils::byteArrayToBase64String(buffer.data());
|
||||
|
||||
emit screenCaptured(screenNumber, Utils::byteArrayToBase64String(buffer.data()));
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
AvAdapter::captureAllScreens()
|
||||
{
|
||||
QtConcurrent::run([this]() {
|
||||
auto screens = QGuiApplication::screens();
|
||||
|
||||
QList<QPixmap> scrs;
|
||||
int width = 0, height = 0, currentPoint = 0;
|
||||
|
||||
foreach (auto scr, screens) {
|
||||
QPixmap pix = scr->grabWindow(0);
|
||||
width += pix.width();
|
||||
if (height < pix.height())
|
||||
height = pix.height();
|
||||
scrs << pix;
|
||||
}
|
||||
|
||||
QPixmap final(width, height);
|
||||
QPainter painter(&final);
|
||||
final.fill(Qt::black);
|
||||
|
||||
foreach (auto scr, scrs) {
|
||||
painter.drawPixmap(QPoint(currentPoint, 0), scr);
|
||||
currentPoint += scr.width();
|
||||
}
|
||||
|
||||
QBuffer buffer;
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
final.save(&buffer, "PNG");
|
||||
emit screenCaptured(-1, Utils::byteArrayToBase64String(buffer.data()));
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
AvAdapter::shareFile(const QString& filePath)
|
||||
{
|
||||
LRCInstance::avModel().setInputFile(filePath);
|
||||
LRCInstance::avModel().setInputFile(filePath, getCurrentCallId());
|
||||
}
|
||||
|
||||
void
|
||||
AvAdapter::shareScreenArea(int screenNumber, int x, int y, int width, int height)
|
||||
AvAdapter::shareScreenArea(unsigned x, unsigned y, unsigned width, unsigned height)
|
||||
{
|
||||
QScreen* screen = qApp->screens().at(screenNumber);
|
||||
if (!screen)
|
||||
return;
|
||||
QRect rect = screen ? screen->geometry() : qApp->primaryScreen()->geometry();
|
||||
#ifdef Q_OS_LINUX
|
||||
int display;
|
||||
|
||||
/*
|
||||
* Provide minimum width, height.
|
||||
* Need to add screen x, y initial value to the setDisplay api call.
|
||||
*/
|
||||
LRCInstance::avModel().setDisplay(screenNumber,
|
||||
rect.x() + x,
|
||||
rect.y() + y,
|
||||
// Get display
|
||||
QString display_env {getenv("DISPLAY")};
|
||||
if (!display_env.isEmpty()) {
|
||||
auto list = display_env.split(":", Qt::SkipEmptyParts);
|
||||
// Should only be one display, so get the first one
|
||||
if (list.size() > 0) {
|
||||
display = list.at(0).toInt();
|
||||
}
|
||||
}
|
||||
|
||||
// xrectsel will freeze all displays too fast so that the call
|
||||
// context menu will not be closed even closed signal is emitted
|
||||
// use timer to wait until popup is closed
|
||||
QTimer::singleShot(100, [=]() mutable {
|
||||
x = y = width = height = 0;
|
||||
xrectsel(&x, &y, &width, &height);
|
||||
|
||||
LRCInstance::avModel().setDisplay(0,
|
||||
x,
|
||||
y,
|
||||
width < 128 ? 128 : width,
|
||||
height < 128 ? 128 : height);
|
||||
height < 128 ? 128 : height,
|
||||
getCurrentCallId());
|
||||
});
|
||||
#else
|
||||
LRCInstance::avModel().setDisplay(0,
|
||||
x,
|
||||
y,
|
||||
width < 128 ? 128 : width,
|
||||
height < 128 ? 128 : height,
|
||||
getCurrentCallId());
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -145,19 +231,24 @@ AvAdapter::stopAudioMeter(bool async)
|
|||
LRCInstance::stopAudioMeter(async);
|
||||
}
|
||||
|
||||
const QString&
|
||||
AvAdapter::getCurrentCallId()
|
||||
{
|
||||
auto* convModel = LRCInstance::getCurrentConversationModel();
|
||||
const auto conversation = convModel->getConversationForUID(LRCInstance::getCurrentConvUid());
|
||||
auto call = LRCInstance::getCallInfoForConversation(conversation);
|
||||
if (!call)
|
||||
return QString();
|
||||
return call->id;
|
||||
}
|
||||
|
||||
void
|
||||
AvAdapter::slotDeviceEvent()
|
||||
{
|
||||
auto& avModel = LRCInstance::avModel();
|
||||
auto defaultDevice = avModel.getDefaultDevice();
|
||||
auto currentCaptureDevice = avModel.getCurrentVideoCaptureDevice();
|
||||
QString callId {};
|
||||
|
||||
auto* convModel = LRCInstance::getCurrentConversationModel();
|
||||
const auto conversation = convModel->getConversationForUID(LRCInstance::getCurrentConvUid());
|
||||
auto call = LRCInstance::getCallInfoForConversation(conversation);
|
||||
if (call)
|
||||
callId = call->id;
|
||||
QString callId = getCurrentCallId();
|
||||
|
||||
/*
|
||||
* Decide whether a device has plugged, unplugged, or nothing has changed.
|
||||
|
|
|
@ -39,6 +39,8 @@ signals:
|
|||
*/
|
||||
void videoDeviceListChanged(bool listIsEmpty);
|
||||
|
||||
void screenCaptured(int screenNumber, QString source);
|
||||
|
||||
protected:
|
||||
void safeInit() override {};
|
||||
|
||||
|
@ -58,9 +60,19 @@ protected:
|
|||
Q_INVOKABLE void shareEntireScreen(int screenNumber);
|
||||
|
||||
/*
|
||||
* Take snap shot of the screen by returning base64 image string.
|
||||
* Share the all screens connected.
|
||||
*/
|
||||
Q_INVOKABLE const QString captureScreen(int screenNumber);
|
||||
Q_INVOKABLE void shareAllScreens();
|
||||
|
||||
/*
|
||||
* Take snap shot of the screen and return emitting signal.
|
||||
*/
|
||||
Q_INVOKABLE void captureScreen(int screenNumber);
|
||||
|
||||
/*
|
||||
* Take snap shot of the all screens and return by emitting signal.
|
||||
*/
|
||||
Q_INVOKABLE void captureAllScreens();
|
||||
|
||||
/*
|
||||
* Share a media file.
|
||||
|
@ -68,14 +80,19 @@ protected:
|
|||
Q_INVOKABLE void shareFile(const QString& filePath);
|
||||
|
||||
/*
|
||||
* Select screen area to display.
|
||||
* Select screen area to display (from all screens).
|
||||
*/
|
||||
Q_INVOKABLE void shareScreenArea(int screenNumber, int x, int y, int width, int height);
|
||||
Q_INVOKABLE void shareScreenArea(unsigned x, unsigned y, unsigned width, unsigned height);
|
||||
|
||||
Q_INVOKABLE void startAudioMeter(bool async);
|
||||
Q_INVOKABLE void stopAudioMeter(bool async);
|
||||
|
||||
private:
|
||||
/*
|
||||
* Get current callId from current selected conv id.
|
||||
*/
|
||||
const QString& getCurrentCallId();
|
||||
|
||||
/*
|
||||
* Used to classify capture device events.
|
||||
*/
|
||||
|
|
|
@ -107,8 +107,16 @@ function addMenuItem(itemName,
|
|||
console.log("Error loading component:",
|
||||
menuItemComponent.errorString())
|
||||
if (menuItemObject !== null) {
|
||||
menuItemObject.clicked.connect(function () {baseContextMenuObject.close()})
|
||||
menuItemObject.clicked.connect(onClickedCallback)
|
||||
menuItemObject.clicked.connect(function () {
|
||||
var callback = function(){
|
||||
onClickedCallback()
|
||||
baseContextMenuObject.onVisibleChanged.disconnect(callback)
|
||||
baseContextMenuObject.close()
|
||||
}
|
||||
|
||||
baseContextMenuObject.onVisibleChanged.connect(callback)
|
||||
baseContextMenuObject.visible = false
|
||||
})
|
||||
menuItemObject.icon.color = "green"
|
||||
|
||||
baseContextMenuObject.addItem(menuItemObject)
|
||||
|
|
|
@ -24,7 +24,9 @@ import QtQuick 2.14
|
|||
Item {
|
||||
readonly property string mainViewLoadPath: "qrc:/src/mainview/MainView.qml"
|
||||
readonly property string wizardViewLoadPath: "qrc:/src/wizardview/WizardView.qml"
|
||||
readonly property string base64StringTitle: "data:image/png;base64,"
|
||||
|
||||
property var mainApplicationScreen: ""
|
||||
property bool callIsFullscreen: false
|
||||
|
||||
TextMetrics {
|
||||
|
|
|
@ -70,7 +70,7 @@ DistantRenderer::getScaledHeight() const
|
|||
void
|
||||
DistantRenderer::paint(QPainter* painter)
|
||||
{
|
||||
auto distantImage = LRCInstance::renderer()->getFrame(distantRenderId_);
|
||||
LRCInstance::renderer()->drawFrame(distantRenderId_, [this, painter](QImage* distantImage) {
|
||||
if (distantImage) {
|
||||
auto scaledDistant = distantImage->scaled(size().toSize(), Qt::KeepAspectRatio);
|
||||
auto tempScaledWidth = static_cast<int>(scaledWidth_ * 1000);
|
||||
|
@ -88,7 +88,11 @@ DistantRenderer::paint(QPainter* painter)
|
|||
or static_cast<int>(scaledHeight_ * 1000) != tempScaledHeight) {
|
||||
emit offsetChanged();
|
||||
}
|
||||
painter->drawImage(QRect(xOffset_, yOffset_, scaledDistant.width(), scaledDistant.height()),
|
||||
painter->drawImage(QRect(xOffset_,
|
||||
yOffset_,
|
||||
scaledDistant.width(),
|
||||
scaledDistant.height()),
|
||||
scaledDistant);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -115,13 +115,11 @@ Item {
|
|||
ContextMenuGenerator.addMenuItem(JamiStrings.shareScreenArea,
|
||||
"qrc:/images/icons/screen_share-24px.svg",
|
||||
function (){
|
||||
if (Qt.application.screens.length === 1) {
|
||||
ScreenRubberBandCreation.createScreenRubberBandWindowObject(
|
||||
null, 0)
|
||||
ScreenRubberBandCreation.showScreenRubberBandWindow()
|
||||
if (Qt.platform.os !== "windows") {
|
||||
AvAdapter.shareScreenArea(0, 0, 0, 0)
|
||||
} else {
|
||||
SelectScreenWindowCreation.createSelectScreenWindowObject(true)
|
||||
SelectScreenWindowCreation.showSelectScreenWindow()
|
||||
ScreenRubberBandCreation.createScreenRubberBandWindowObject()
|
||||
ScreenRubberBandCreation.showScreenRubberBandWindow()
|
||||
}
|
||||
})
|
||||
ContextMenuGenerator.addMenuItem(JamiStrings.shareFile,
|
||||
|
|
|
@ -51,7 +51,7 @@ Rectangle {
|
|||
if (avatar === "") {
|
||||
contactImage.source = ""
|
||||
} else {
|
||||
contactImage.source = "data:image/png;base64," + avatar
|
||||
contactImage.source = JamiQmlUtils.base64StringTitle + avatar
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,16 +32,32 @@ import net.jami.Constants 1.0
|
|||
Window {
|
||||
id: screenRubberBandWindow
|
||||
|
||||
property int screenNumber: 0
|
||||
function setAllScreensGeo() {
|
||||
var width = 0, height = 0
|
||||
var screens = Qt.application.screens
|
||||
for (var i = 0; i < screens.length; ++i) {
|
||||
width += screens[i].width
|
||||
if (height < screens[i].height)
|
||||
height = screens[i].height
|
||||
}
|
||||
|
||||
screenRubberBandWindow.width = width
|
||||
screenRubberBandWindow.height = height
|
||||
screenRubberBandWindow.x = 0
|
||||
screenRubberBandWindow.y = 0
|
||||
}
|
||||
|
||||
flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.WA_TranslucentBackground
|
||||
|
||||
|
||||
// Opacity with 0.7 window that will fill the entire screen,
|
||||
// provide the users to select the area that they
|
||||
// want to share.
|
||||
color: Qt.rgba(0, 0, 0, 0.7)
|
||||
// +1 so that it does not fallback to the previous screen
|
||||
x: screen.virtualX + 1
|
||||
y: screen.virtualY + 1
|
||||
|
||||
screen: Qt.application.screens[0]
|
||||
|
||||
// Rect for selection.
|
||||
Rectangle {
|
||||
|
@ -67,7 +83,6 @@ Window {
|
|||
hoverEnabled: true
|
||||
cursorShape: Qt.CrossCursor
|
||||
|
||||
|
||||
// Geo changing for user selection.
|
||||
onPressed: {
|
||||
originalX = mouseX
|
||||
|
@ -97,7 +112,7 @@ Window {
|
|||
|
||||
onReleased: {
|
||||
recSelect.visible = false
|
||||
AvAdapter.shareScreenArea(screenNumber, recSelect.x, recSelect.y,
|
||||
AvAdapter.shareScreenArea(recSelect.x, recSelect.y,
|
||||
recSelect.width, recSelect.height)
|
||||
screenRubberBandWindow.close()
|
||||
}
|
||||
|
|
|
@ -37,9 +37,7 @@ Window {
|
|||
property int minHeight: 500
|
||||
|
||||
property int selectedScreenNumber: -1
|
||||
|
||||
// Decide whether to show screen area or entire screen.
|
||||
property bool selectArea: false
|
||||
property bool selectAllScreens: false
|
||||
|
||||
// How many rows the ScrollView should have.
|
||||
function calculateRepeaterModel() {
|
||||
|
@ -55,10 +53,10 @@ Window {
|
|||
minimumWidth: minWidth
|
||||
minimumHeight: minHeight
|
||||
|
||||
title: "Screen sharing"
|
||||
width: minWidth
|
||||
height: minHeight
|
||||
|
||||
// Note: Qt.application.screens[0] is the app's current existing screen.
|
||||
screen: Qt.application.screens[0]
|
||||
screen: JamiQmlUtils.mainApplicationScreen
|
||||
|
||||
modality: Qt.ApplicationModal
|
||||
|
||||
|
@ -96,6 +94,7 @@ Window {
|
|||
clip: true
|
||||
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
|
||||
|
||||
// Column of rows repeater (two screen captures in a row).
|
||||
Column {
|
||||
|
@ -125,6 +124,21 @@ Window {
|
|||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AvAdapter
|
||||
|
||||
function onScreenCaptured(screenNumber, source) {
|
||||
if (screenNumber === -1)
|
||||
screenShotAll.source = JamiQmlUtils.base64StringTitle + source
|
||||
if (screenNumber !== index && screenNumber !== index + 1)
|
||||
return
|
||||
if (screenNumber % 2 !== 1)
|
||||
screenShotOdd.source = JamiQmlUtils.base64StringTitle + source
|
||||
else
|
||||
screenShotEven.source = JamiQmlUtils.base64StringTitle + source
|
||||
}
|
||||
}
|
||||
|
||||
// To make sure that two screen captures in one row,
|
||||
// a repeater of two rect is needed, which one in charge
|
||||
// of odd number screen, one in charge of even number screen.
|
||||
|
@ -154,12 +168,9 @@ Window {
|
|||
fillMode: Image.PreserveAspectFit
|
||||
mipmap: true
|
||||
|
||||
Component.onCompleted: {
|
||||
screenShotOdd.source = "data:image/png;base64,"
|
||||
+ AvAdapter.captureScreen(
|
||||
Component.onCompleted: AvAdapter.captureScreen(
|
||||
calculateScreenNumber(index, false) - 1)
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: screenNameOdd
|
||||
|
@ -224,8 +235,7 @@ Window {
|
|||
|
||||
Component.onCompleted: {
|
||||
if (screenSelectionRectEven.visible)
|
||||
screenShotEven.source = "data:image/png;base64,"
|
||||
+ AvAdapter.captureScreen(
|
||||
AvAdapter.captureScreen(
|
||||
calculateScreenNumber(index, true) - 1)
|
||||
}
|
||||
}
|
||||
|
@ -259,6 +269,71 @@ Window {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: screenSelectionRectAll
|
||||
|
||||
property string borderColor: JamiTheme.tabbarBorderColor
|
||||
|
||||
anchors.horizontalCenter: screenSelectionScrollViewColumn.horizontalCenter
|
||||
|
||||
color: JamiTheme.secondaryBackgroundColor
|
||||
|
||||
height: screenSelectionScrollView.height
|
||||
width: screenSelectionScrollView.width - 2 * JamiTheme.preferredMarginSize
|
||||
|
||||
border.color: borderColor
|
||||
|
||||
Connections {
|
||||
target: selectScreenWindow
|
||||
|
||||
function onSelectedScreenNumberChanged() {
|
||||
// Recover from green state.
|
||||
selectAllScreens = false
|
||||
screenSelectionRectAll.borderColor = JamiTheme.tabbarBorderColor
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: screenShotAll
|
||||
|
||||
anchors.top: screenSelectionRectAll.top
|
||||
anchors.topMargin: 10
|
||||
anchors.horizontalCenter: screenSelectionRectAll.horizontalCenter
|
||||
|
||||
height: screenSelectionRectAll.height - 50
|
||||
width: screenSelectionRectAll.width - 50
|
||||
|
||||
fillMode: Image.PreserveAspectFit
|
||||
mipmap: true
|
||||
|
||||
Component.onCompleted: AvAdapter.captureAllScreens()
|
||||
}
|
||||
|
||||
Text {
|
||||
id: screenNameAll
|
||||
|
||||
anchors.top: screenShotAll.bottom
|
||||
anchors.topMargin: 10
|
||||
anchors.horizontalCenter: screenSelectionRectAll.horizontalCenter
|
||||
|
||||
font.pointSize: JamiTheme.textFontSize - 2
|
||||
text: qsTr("All Screens")
|
||||
color: JamiTheme.textColor
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
|
||||
onClicked: {
|
||||
selectedScreenNumber = -1
|
||||
selectAllScreens = true
|
||||
screenSelectionRectAll.borderColor
|
||||
= JamiTheme.screenSelectionBorderGreen
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -273,7 +348,7 @@ Window {
|
|||
width: 200
|
||||
height: 36
|
||||
|
||||
visible: selectedScreenNumber != -1
|
||||
visible: selectedScreenNumber != -1 || selectAllScreens
|
||||
|
||||
color: JamiTheme.buttonTintedBlack
|
||||
hoveredColor: JamiTheme.buttonTintedBlackHovered
|
||||
|
@ -284,21 +359,11 @@ Window {
|
|||
text: JamiStrings.shareScreen
|
||||
|
||||
onClicked: {
|
||||
if (selectArea) {
|
||||
selectScreenWindow.hide()
|
||||
ScreenRubberBandCreation.createScreenRubberBandWindowObject(
|
||||
selectScreenWindow, selectedScreenNumber - 1)
|
||||
ScreenRubberBandCreation.showScreenRubberBandWindow()
|
||||
|
||||
|
||||
// Destory selectScreenWindow once screenRubberBand is closed.
|
||||
ScreenRubberBandCreation.connectOnClosingEvent(function () {
|
||||
selectScreenWindow.close()
|
||||
})
|
||||
} else {
|
||||
if (selectAllScreens)
|
||||
AvAdapter.shareAllScreens()
|
||||
else
|
||||
AvAdapter.shareEntireScreen(selectedScreenNumber - 1)
|
||||
selectScreenWindow.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Window 2.14
|
||||
import net.jami.Models 1.0
|
||||
|
||||
Window {
|
||||
id: videoWindow
|
||||
|
@ -28,7 +29,11 @@ Window {
|
|||
|
||||
flags: Qt.FramelessWindowHint
|
||||
|
||||
screen: Qt.application.screens[0]
|
||||
screen: JamiQmlUtils.mainApplicationScreen
|
||||
|
||||
// +1 so that it does not fallback to the previous screen
|
||||
x: screen.virtualX + 1
|
||||
y: screen.virtualY + 1
|
||||
|
||||
visible: false
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ function finishCreation() {
|
|||
|
||||
// Signal connection.
|
||||
callFullScreenWindowContainerObject.onClosing.connect(
|
||||
destoryVideoCallFullScreenWindowContainer)
|
||||
destroyVideoCallFullScreenWindowContainer)
|
||||
}
|
||||
|
||||
function checkIfVisible() {
|
||||
|
@ -57,7 +57,7 @@ function setAsContainerChild(obj) {
|
|||
}
|
||||
|
||||
// Destroy and reset callFullScreenWindowContainerObject when window is closed.
|
||||
function destoryVideoCallFullScreenWindowContainer() {
|
||||
function destroyVideoCallFullScreenWindowContainer() {
|
||||
if (!callFullScreenWindowContainerObject)
|
||||
return
|
||||
callFullScreenWindowContainerObject.destroy()
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) 2020 by Savoir-faire Linux
|
||||
* Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
|
||||
|
@ -17,64 +16,45 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Global screen rubber band window component, object variable for creation.
|
||||
*/
|
||||
// Global screen rubber band window component, object variable for creation.
|
||||
var screenRubberBandWindowComponent
|
||||
var screenRubberBandWindowObject
|
||||
|
||||
function createScreenRubberBandWindowObject(parent, screenNumber) {
|
||||
var selectAllScreens = false
|
||||
|
||||
function createScreenRubberBandWindowObject() {
|
||||
if (screenRubberBandWindowObject)
|
||||
return
|
||||
screenRubberBandWindowComponent = Qt.createComponent(
|
||||
"../components/ScreenRubberBand.qml")
|
||||
if (screenRubberBandWindowComponent.status === Component.Ready)
|
||||
finishCreation(parent, screenNumber)
|
||||
finishCreation()
|
||||
else if (screenRubberBandWindowComponent.status === Component.Error)
|
||||
console.log("Error loading component:",
|
||||
screenRubberBandWindowComponent.errorString())
|
||||
}
|
||||
|
||||
function finishCreation(parent, screenNumber) {
|
||||
screenRubberBandWindowObject = screenRubberBandWindowComponent.createObject(
|
||||
parent)
|
||||
function finishCreation() {
|
||||
screenRubberBandWindowObject = screenRubberBandWindowComponent.createObject()
|
||||
if (screenRubberBandWindowObject === null) {
|
||||
|
||||
|
||||
/*
|
||||
* Error Handling.
|
||||
*/
|
||||
// Error Handling.
|
||||
console.log("Error creating screen rubber band object")
|
||||
}
|
||||
|
||||
screenRubberBandWindowObject.screenNumber = screenNumber
|
||||
screenRubberBandWindowObject.screen = Qt.application.screens[screenNumber]
|
||||
|
||||
|
||||
/*
|
||||
* Signal connection.
|
||||
*/
|
||||
// Signal connection.
|
||||
screenRubberBandWindowObject.onClosing.connect(
|
||||
destoryScreenRubberBandWindow)
|
||||
destroyScreenRubberBandWindow)
|
||||
}
|
||||
|
||||
function showScreenRubberBandWindow() {
|
||||
screenRubberBandWindowObject.showFullScreen()
|
||||
screenRubberBandWindowObject.show()
|
||||
screenRubberBandWindowObject.setAllScreensGeo()
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Destroy and reset screenRubberBandWindowObject when window is closed.
|
||||
*/
|
||||
function destoryScreenRubberBandWindow() {
|
||||
// Destroy and reset screenRubberBandWindowObject when window is closed.
|
||||
function destroyScreenRubberBandWindow() {
|
||||
if (!screenRubberBandWindowObject)
|
||||
return
|
||||
screenRubberBandWindowObject.destroy()
|
||||
screenRubberBandWindowObject = false
|
||||
}
|
||||
|
||||
function connectOnClosingEvent(func) {
|
||||
if (screenRubberBandWindowObject)
|
||||
screenRubberBandWindowObject.onClosing.connect(func)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) 2020 by Savoir-faire Linux
|
||||
* Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
|
||||
|
@ -17,54 +16,45 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Global select screen window component, object variable for creation.
|
||||
*/
|
||||
// Global select screen window component, object variable for creation.
|
||||
var selectScreenWindowComponent
|
||||
var selectScreenWindowObject
|
||||
|
||||
function createSelectScreenWindowObject(selectArea = false) {
|
||||
function createSelectScreenWindowObject() {
|
||||
if (selectScreenWindowObject)
|
||||
return
|
||||
selectScreenWindowComponent = Qt.createComponent(
|
||||
"../components/SelectScreen.qml")
|
||||
if (selectScreenWindowComponent.status === Component.Ready)
|
||||
finishCreation(selectArea)
|
||||
finishCreation()
|
||||
else if (selectScreenWindowComponent.status === Component.Error)
|
||||
console.log("Error loading component:",
|
||||
selectScreenWindowComponent.errorString())
|
||||
}
|
||||
|
||||
function finishCreation(selectArea) {
|
||||
function finishCreation() {
|
||||
selectScreenWindowObject = selectScreenWindowComponent.createObject()
|
||||
if (selectScreenWindowObject === null) {
|
||||
|
||||
|
||||
/*
|
||||
* Error Handling.
|
||||
*/
|
||||
// Error Handling.
|
||||
console.log("Error creating select screen object")
|
||||
}
|
||||
|
||||
selectScreenWindowObject.selectArea = selectArea
|
||||
|
||||
|
||||
/*
|
||||
* Signal connection.
|
||||
*/
|
||||
selectScreenWindowObject.onClosing.connect(destorySelectScreenWindow)
|
||||
// Signal connection.
|
||||
selectScreenWindowObject.onClosing.connect(destroySelectScreenWindow)
|
||||
}
|
||||
|
||||
function showSelectScreenWindow() {
|
||||
selectScreenWindowObject.show()
|
||||
|
||||
var screen = selectScreenWindowObject.screen
|
||||
selectScreenWindowObject.x = screen.virtualX +
|
||||
(screen.width - selectScreenWindowObject.width) / 2
|
||||
selectScreenWindowObject.y = screen.virtualY +
|
||||
(screen.height - selectScreenWindowObject.height) / 2
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Destroy and reset selectScreenWindowObject when window is closed.
|
||||
*/
|
||||
function destorySelectScreenWindow() {
|
||||
// Destroy and reset selectScreenWindowObject when window is closed.
|
||||
function destroySelectScreenWindow() {
|
||||
if(!selectScreenWindowObject)
|
||||
return
|
||||
selectScreenWindowObject.destroy()
|
||||
|
|
|
@ -31,25 +31,23 @@ PreviewRenderer::PreviewRenderer(QQuickItem* parent)
|
|||
|
||||
previewFrameUpdatedConnection_ = connect(LRCInstance::renderer(),
|
||||
&RenderManager::previewFrameUpdated,
|
||||
[this]() { update(QRect(0, 0, width(), height())); });
|
||||
|
||||
previewRenderingStopped_ = connect(LRCInstance::renderer(),
|
||||
&RenderManager::previewRenderingStopped,
|
||||
[this]() { update(QRect(0, 0, width(), height())); });
|
||||
[this]() {
|
||||
if (isVisible())
|
||||
update(QRect(0, 0, width(), height()));
|
||||
});
|
||||
}
|
||||
|
||||
PreviewRenderer::~PreviewRenderer()
|
||||
{
|
||||
disconnect(previewFrameUpdatedConnection_);
|
||||
disconnect(previewRenderingStopped_);
|
||||
}
|
||||
|
||||
void
|
||||
PreviewRenderer::paint(QPainter* painter)
|
||||
{
|
||||
auto previewImage = LRCInstance::renderer()->getPreviewFrame();
|
||||
LRCInstance::renderer()
|
||||
->drawFrame(lrc::api::video::PREVIEW_RENDERER_ID, [this, painter](QImage* previewImage) {
|
||||
if (previewImage) {
|
||||
QImage scaledPreview;
|
||||
auto aspectRatio = static_cast<qreal>(previewImage->width())
|
||||
/ static_cast<qreal>(previewImage->height());
|
||||
auto previewHeight = height();
|
||||
|
@ -60,17 +58,21 @@ PreviewRenderer::paint(QPainter* painter)
|
|||
* e.g.
|
||||
* auto parent = qobject_cast<QWidget*>(this->parent());
|
||||
* auto xPos = (parent->width() - previewWidth) / 2;
|
||||
* setGeometry(QRect(QPoint(xPos, this->pos().y()), QSize(previewWidth, previewHeight)));
|
||||
* setGeometry(QRect(QPoint(xPos, this->pos().y()),
|
||||
* QSize(previewWidth, previewHeight)));
|
||||
*/
|
||||
setWidth(previewWidth);
|
||||
setHeight(previewHeight);
|
||||
|
||||
// If the given size is empty, this function returns a null image.
|
||||
QImage scaledPreview;
|
||||
scaledPreview = previewImage->scaled(size().toSize(), Qt::KeepAspectRatio);
|
||||
painter->drawImage(QRect(0, 0, scaledPreview.width(), scaledPreview.height()),
|
||||
scaledPreview);
|
||||
} else {
|
||||
paintBackground(painter);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -93,7 +95,8 @@ VideoCallPreviewRenderer::~VideoCallPreviewRenderer() {}
|
|||
void
|
||||
VideoCallPreviewRenderer::paint(QPainter* painter)
|
||||
{
|
||||
auto previewImage = LRCInstance::renderer()->getPreviewFrame();
|
||||
LRCInstance::renderer()
|
||||
->drawFrame(lrc::api::video::PREVIEW_RENDERER_ID, [this, painter](QImage* previewImage) {
|
||||
if (previewImage) {
|
||||
auto scalingFactor = static_cast<qreal>(previewImage->height())
|
||||
/ static_cast<qreal>(previewImage->width());
|
||||
|
@ -103,6 +106,7 @@ VideoCallPreviewRenderer::paint(QPainter* painter)
|
|||
painter->drawImage(QRect(0, 0, scaledPreview.width(), scaledPreview.height()),
|
||||
scaledPreview);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
PhotoboothPreviewRender::PhotoboothPreviewRender(QQuickItem* parent)
|
||||
|
@ -130,7 +134,8 @@ PhotoboothPreviewRender::paint(QPainter* painter)
|
|||
{
|
||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
auto previewImage = LRCInstance::renderer()->getPreviewFrame();
|
||||
LRCInstance::renderer()
|
||||
->drawFrame(lrc::api::video::PREVIEW_RENDERER_ID, [this, painter](QImage* previewImage) {
|
||||
if (previewImage) {
|
||||
QImage scaledPreview;
|
||||
scaledPreview = Utils::getCirclePhoto(*previewImage,
|
||||
|
@ -138,4 +143,5 @@ PhotoboothPreviewRender::paint(QPainter* painter)
|
|||
painter->drawImage(QRect(0, 0, scaledPreview.width(), scaledPreview.height()),
|
||||
scaledPreview);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -38,7 +38,6 @@ protected:
|
|||
|
||||
private:
|
||||
QMetaObject::Connection previewFrameUpdatedConnection_;
|
||||
QMetaObject::Connection previewRenderingStopped_;
|
||||
};
|
||||
|
||||
class VideoCallPreviewRenderer : public PreviewRenderer
|
||||
|
|
|
@ -85,7 +85,10 @@ FrameWrapper::stopRendering()
|
|||
QImage*
|
||||
FrameWrapper::getFrame()
|
||||
{
|
||||
return isRendering_ ? image_.get() : nullptr;
|
||||
if (image_.get()) {
|
||||
return isRendering_ ? (image_.get()->isNull() ? nullptr : image_.get()) : nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -94,6 +97,18 @@ FrameWrapper::isRendering()
|
|||
return isRendering_;
|
||||
}
|
||||
|
||||
bool
|
||||
FrameWrapper::frameMutexTryLock()
|
||||
{
|
||||
return mutex_.tryLock();
|
||||
}
|
||||
|
||||
void
|
||||
FrameWrapper::frameMutexUnlock()
|
||||
{
|
||||
mutex_.unlock();
|
||||
}
|
||||
|
||||
void
|
||||
FrameWrapper::slotRenderingStarted(const QString& id)
|
||||
{
|
||||
|
@ -127,23 +142,25 @@ FrameWrapper::slotFrameUpdated(const QString& id)
|
|||
|
||||
unsigned int width = renderer_->size().width();
|
||||
unsigned int height = renderer_->size().height();
|
||||
|
||||
#ifndef Q_OS_LINUX
|
||||
unsigned int size = frame_.storage.size();
|
||||
auto imageFormat = QImage::Format_ARGB32_Premultiplied;
|
||||
#else
|
||||
unsigned int size = frame_.size;
|
||||
auto imageFormat = QImage::Format_ARGB32;
|
||||
#endif
|
||||
/*
|
||||
* If the frame is empty or not the expected size,
|
||||
* do nothing and keep the last rendered QImage.
|
||||
*/
|
||||
if (size != 0 && size == width * height * 4) {
|
||||
#ifndef Q_OS_LINUX
|
||||
buffer_ = std::move(frame_.storage);
|
||||
image_.reset(new QImage((uchar*) buffer_.data(),
|
||||
width,
|
||||
height,
|
||||
QImage::Format_ARGB32_Premultiplied));
|
||||
#else
|
||||
if (frame_.ptr) {
|
||||
image_.reset(new QImage(frame_.ptr, width, height, QImage::Format_ARGB32));
|
||||
buffer_.reserve(size);
|
||||
std::move(frame_.ptr, frame_.ptr + size, buffer_.begin());
|
||||
#endif
|
||||
image_.reset(new QImage((uchar*) buffer_.data(), width, height, imageFormat));
|
||||
}
|
||||
}
|
||||
emit frameUpdated(id);
|
||||
|
@ -161,7 +178,10 @@ FrameWrapper::slotRenderingStopped(const QString& id)
|
|||
|
||||
renderer_ = nullptr;
|
||||
|
||||
{
|
||||
QMutexLocker lock(&mutex_);
|
||||
image_.reset();
|
||||
}
|
||||
|
||||
emit renderingStopped(id);
|
||||
}
|
||||
|
@ -202,12 +222,6 @@ RenderManager::isPreviewing()
|
|||
return previewFrameWrapper_->isRendering();
|
||||
}
|
||||
|
||||
QImage*
|
||||
RenderManager::getPreviewFrame()
|
||||
{
|
||||
return previewFrameWrapper_->getFrame();
|
||||
}
|
||||
|
||||
void
|
||||
RenderManager::stopPreviewing()
|
||||
{
|
||||
|
@ -232,16 +246,6 @@ RenderManager::startPreviewing(bool force)
|
|||
avModel_.startPreview();
|
||||
}
|
||||
|
||||
QImage*
|
||||
RenderManager::getFrame(const QString& id)
|
||||
{
|
||||
auto dfwIt = distantFrameWrapperMap_.find(id);
|
||||
if (dfwIt != distantFrameWrapperMap_.end()) {
|
||||
return dfwIt->second->getFrame();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
RenderManager::addDistantRenderer(const QString& id)
|
||||
{
|
||||
|
@ -305,3 +309,28 @@ RenderManager::removeDistantRenderer(const QString& id)
|
|||
distantFrameWrapperMap_.erase(dfwIt);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RenderManager::drawFrame(const QString& id, DrawFrameCallback cb)
|
||||
{
|
||||
if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
|
||||
if (previewFrameWrapper_->frameMutexTryLock()) {
|
||||
cb(previewFrameWrapper_->getFrame());
|
||||
previewFrameWrapper_->frameMutexUnlock();
|
||||
}
|
||||
} else {
|
||||
auto dfwIt = distantFrameWrapperMap_.find(id);
|
||||
if (dfwIt != distantFrameWrapperMap_.end()) {
|
||||
if (dfwIt->second->frameMutexTryLock()) {
|
||||
cb(dfwIt->second->getFrame());
|
||||
dfwIt->second->frameMutexUnlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QImage*
|
||||
RenderManager::getPreviewFrame()
|
||||
{
|
||||
return previewFrameWrapper_->getFrame();
|
||||
}
|
||||
|
|
|
@ -77,6 +77,10 @@ public:
|
|||
*/
|
||||
bool isRendering();
|
||||
|
||||
bool frameMutexTryLock();
|
||||
|
||||
void frameMutexUnlock();
|
||||
|
||||
signals:
|
||||
/*
|
||||
* Emitted each time a frame is ready to be displayed.
|
||||
|
@ -168,15 +172,12 @@ public:
|
|||
explicit RenderManager(AVModel& avModel);
|
||||
~RenderManager();
|
||||
|
||||
using DrawFrameCallback = std::function<void(QImage*)>;
|
||||
|
||||
/*
|
||||
* Check if the preview is active.
|
||||
*/
|
||||
bool isPreviewing();
|
||||
/*
|
||||
* Get the most recently rendered preview frame as a QImage.
|
||||
* @return the rendered preview image
|
||||
*/
|
||||
QImage* getPreviewFrame();
|
||||
/*
|
||||
* Start capturing and rendering preview frames.
|
||||
* @param force if the capture device should be started
|
||||
|
@ -186,13 +187,6 @@ public:
|
|||
* Stop capturing.
|
||||
*/
|
||||
void stopPreviewing();
|
||||
|
||||
/*
|
||||
* Get the most recently rendered distant frame for a given id
|
||||
* as a QImage.
|
||||
* @return the rendered preview image
|
||||
*/
|
||||
QImage* getFrame(const QString& id);
|
||||
/*
|
||||
* Add and connect a distant renderer for a given id
|
||||
* to a FrameWrapper object
|
||||
|
@ -205,6 +199,18 @@ public:
|
|||
* @param id
|
||||
*/
|
||||
void removeDistantRenderer(const QString& id);
|
||||
/*
|
||||
* Frame will be provided in the callback thread safely
|
||||
* @param id
|
||||
* @param cb
|
||||
*/
|
||||
void drawFrame(const QString& id, DrawFrameCallback cb);
|
||||
|
||||
/*
|
||||
* Get the most recently rendered preview frame as a QImage (none thread safe).
|
||||
* @return the rendered preview image
|
||||
*/
|
||||
QImage* getPreviewFrame();
|
||||
|
||||
signals:
|
||||
|
||||
|
|
123
src/xrectsel.c
Normal file
123
src/xrectsel.c
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* This code is based and adapted from:
|
||||
* https://github.com/lolilolicon/FFcast2/blob/master/xrectsel.c
|
||||
*
|
||||
* now located at:
|
||||
* https://github.com/lolilolicon/xrectsel/blob/master/xrectsel.c
|
||||
*
|
||||
* xrectsel.c -- print the geometry of a rectangular screen region.
|
||||
* Copyright (C) 2011-2014 lolilolicon <lolilolicon@gmail.com>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/cursorfont.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void
|
||||
xrectsel(unsigned* x_sel, unsigned* y_sel, unsigned* w_sel, unsigned* h_sel)
|
||||
{
|
||||
Display* dpy = XOpenDisplay(NULL);
|
||||
if (!dpy)
|
||||
return;
|
||||
|
||||
Window root = DefaultRootWindow(dpy);
|
||||
|
||||
XEvent ev;
|
||||
|
||||
GC sel_gc;
|
||||
XGCValues sel_gv;
|
||||
|
||||
int btn_pressed = 0;
|
||||
int x = 0, y = 0;
|
||||
unsigned int width = 0, height = 0;
|
||||
int start_x = 0, start_y = 0;
|
||||
|
||||
Cursor cursor;
|
||||
cursor = XCreateFontCursor(dpy, XC_crosshair);
|
||||
|
||||
/* Grab pointer for these events */
|
||||
XGrabPointer(dpy,
|
||||
root,
|
||||
True,
|
||||
PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
|
||||
GrabModeAsync,
|
||||
GrabModeAsync,
|
||||
None,
|
||||
cursor,
|
||||
CurrentTime);
|
||||
|
||||
sel_gv.function = GXinvert;
|
||||
sel_gv.subwindow_mode = IncludeInferiors;
|
||||
sel_gv.line_width = 1;
|
||||
sel_gc = XCreateGC(dpy, root, GCFunction | GCSubwindowMode | GCLineWidth, &sel_gv);
|
||||
|
||||
for (;;) {
|
||||
XNextEvent(dpy, &ev);
|
||||
|
||||
if (ev.type == ButtonPress) {
|
||||
btn_pressed = 1;
|
||||
x = start_x = ev.xbutton.x_root;
|
||||
y = start_y = ev.xbutton.y_root;
|
||||
width = height = 0;
|
||||
|
||||
} else if (ev.type == MotionNotify) {
|
||||
if (!btn_pressed)
|
||||
continue; /* Draw only if button is pressed */
|
||||
|
||||
/* Re-draw last Rectangle to clear it */
|
||||
XDrawRectangle(dpy, root, sel_gc, x, y, width, height);
|
||||
|
||||
x = ev.xbutton.x_root;
|
||||
y = ev.xbutton.y_root;
|
||||
|
||||
if (x > start_x) {
|
||||
width = x - start_x;
|
||||
x = start_x;
|
||||
} else {
|
||||
width = start_x - x;
|
||||
}
|
||||
|
||||
if (y > start_y) {
|
||||
height = y - start_y;
|
||||
y = start_y;
|
||||
} else {
|
||||
height = start_y - y;
|
||||
}
|
||||
|
||||
/* Draw Rectangle */
|
||||
XDrawRectangle(dpy, root, sel_gc, x, y, width, height);
|
||||
XFlush(dpy);
|
||||
|
||||
} else if (ev.type == ButtonRelease)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Re-draw last Rectangle to clear it */
|
||||
XDrawRectangle(dpy, root, sel_gc, x, y, width, height);
|
||||
XFlush(dpy);
|
||||
|
||||
XUngrabPointer(dpy, CurrentTime);
|
||||
XFreeCursor(dpy, cursor);
|
||||
XFreeGC(dpy, sel_gc);
|
||||
XSync(dpy, 1);
|
||||
|
||||
*x_sel = x;
|
||||
*y_sel = y;
|
||||
*w_sel = width;
|
||||
*h_sel = height;
|
||||
|
||||
XCloseDisplay(dpy);
|
||||
}
|
26
src/xrectsel.h
Normal file
26
src/xrectsel.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* This code is based and adapted from:
|
||||
* https://github.com/lolilolicon/FFcast2/blob/master/xrectsel.c
|
||||
*
|
||||
* now located at:
|
||||
* https://github.com/lolilolicon/xrectsel/blob/master/xrectsel.c
|
||||
*
|
||||
* xrectsel.c -- print the geometry of a rectangular screen region.
|
||||
* Copyright (C) 2011-2014 lolilolicon <lolilolicon@gmail.com>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
extern "C" {
|
||||
void xrectsel(unsigned* x_sel, unsigned* y_sel, unsigned* w_sel, unsigned* h_sel);
|
||||
}
|
Loading…
Add table
Reference in a new issue