live change resolution (WIP)

This commit is contained in:
Craig
2020-02-11 05:15:59 +00:00
parent 3d1341cfe1
commit 9e995233af
27 changed files with 747 additions and 127 deletions

View File

@ -44,6 +44,7 @@ type Pipeline struct {
Sample chan types.Sample
CodecName string
ClockRate float32
Src string
id int
}
@ -55,6 +56,8 @@ const (
videoClockRate = 90000
audioClockRate = 48000
pcmClockRate = 8000
videoSrc = "ximagesrc xid=%s show-pointer=true use-damage=false ! video/x-raw ! videoconvert ! queue ! "
audioSrc = "pulsesrc device=%s ! audioconvert ! "
)
func init() {
@ -63,8 +66,9 @@ func init() {
}
// CreatePipeline creates a GStreamer Pipeline
func CreatePipeline(codecName string, pipelineSrc string) (*Pipeline, error) {
pipelineStr := "appsink name=appsink"
func CreatePipeline(codecName string, pipelineDevice string, pipelineSrc string) (*Pipeline, error) {
pipelineStr := " ! appsink name=appsink"
var clockRate float32
switch codecName {
@ -72,93 +76,119 @@ func CreatePipeline(codecName string, pipelineSrc string) (*Pipeline, error) {
// https://gstreamer.freedesktop.org/documentation/vpx/vp8enc.html?gi-language=c
// gstreamer1.0-plugins-good
// vp8enc error-resilient=partitions keyframe-max-dist=10 auto-alt-ref=true cpu-used=5 deadline=1
pipelineStr = pipelineSrc + " ! vp8enc cpu-used=8 threads=2 deadline=1 error-resilient=partitions keyframe-max-dist=10 auto-alt-ref=true ! " + pipelineStr
clockRate = videoClockRate
if err := CheckPlugins([]string{"ximagesrc", "vpx"}); err != nil {
return nil, err
}
clockRate = videoClockRate
if pipelineSrc != "" {
pipelineStr = fmt.Sprintf(pipelineSrc+pipelineStr, pipelineDevice)
} else {
pipelineStr = fmt.Sprintf(videoSrc+"vp8enc cpu-used=8 threads=2 deadline=1 error-resilient=partitions keyframe-max-dist=10 auto-alt-ref=true"+pipelineStr, pipelineDevice)
}
case webrtc.VP9:
// https://gstreamer.freedesktop.org/documentation/vpx/vp9enc.html?gi-language=c
// gstreamer1.0-plugins-good
// vp9enc
// Causes panic! not sure why...
pipelineStr = pipelineSrc + " ! vp9enc ! " + pipelineStr
clockRate = videoClockRate
if err := CheckPlugins([]string{"ximagesrc", "vpx"}); err != nil {
return nil, err
}
clockRate = videoClockRate
// Causes panic! not sure why...
if pipelineSrc != "" {
pipelineStr = fmt.Sprintf(pipelineSrc+pipelineStr, pipelineDevice)
} else {
pipelineStr = fmt.Sprintf(videoSrc+"vp9enc"+pipelineStr, pipelineDevice)
}
case webrtc.H264:
// https://gstreamer.freedesktop.org/documentation/openh264/openh264enc.html?gi-language=c#openh264enc
// gstreamer1.0-plugins-bad
// openh264enc multi-thread=4 complexity=high bitrate=3072000 max-bitrate=4096000
pipelineStr = pipelineSrc + " ! openh264enc multi-thread=4 complexity=high bitrate=3072000 max-bitrate=4096000 ! video/x-h264,stream-format=byte-stream ! " + pipelineStr
clockRate = videoClockRate
if err := CheckPlugins([]string{"ximagesrc"}); err != nil {
return nil, err
}
// https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c
// gstreamer1.0-plugins-ugly
// video/x-raw,format=I420 ! x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream
if err := CheckPlugins([]string{"openh264"}); err != nil {
pipelineStr = pipelineSrc + " ! video/x-raw,format=I420 ! x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream ! " + pipelineStr
clockRate = videoClockRate
if err := CheckPlugins([]string{"x264"}); err != nil {
return nil, err
if pipelineSrc != "" {
pipelineStr = fmt.Sprintf(pipelineSrc+pipelineStr, pipelineDevice)
} else {
pipelineStr = fmt.Sprintf(videoSrc+"openh264enc multi-thread=4 complexity=high bitrate=3072000 max-bitrate=4096000 ! video/x-h264,stream-format=byte-stream"+pipelineStr, pipelineDevice)
// https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c
// gstreamer1.0-plugins-ugly
// video/x-raw,format=I420 ! x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream
if err := CheckPlugins([]string{"openh264"}); err != nil {
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=I420 ! x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream"+pipelineStr, pipelineDevice)
if err := CheckPlugins([]string{"x264"}); err != nil {
return nil, err
}
}
}
case webrtc.Opus:
// https://gstreamer.freedesktop.org/documentation/opus/opusenc.html
// gstreamer1.0-plugins-base
// opusenc
pipelineStr = pipelineSrc + " ! opusenc ! " + pipelineStr
clockRate = audioClockRate
if err := CheckPlugins([]string{"pulseaudio", "opus"}); err != nil {
return nil, err
}
clockRate = audioClockRate
if pipelineSrc != "" {
pipelineStr = fmt.Sprintf(pipelineSrc+pipelineStr, pipelineDevice)
} else {
pipelineStr = fmt.Sprintf(audioSrc+"opusenc"+pipelineStr, pipelineDevice)
}
case webrtc.G722:
// https://gstreamer.freedesktop.org/documentation/libav/avenc_g722.html?gi-language=c
// gstreamer1.0-libav
// avenc_g722
pipelineStr = pipelineSrc + " ! avenc_g722 ! " + pipelineStr
clockRate = audioClockRate
if err := CheckPlugins([]string{"pulseaudio", "libav"}); err != nil {
return nil, err
}
clockRate = audioClockRate
if pipelineSrc != "" {
pipelineStr = fmt.Sprintf(pipelineSrc+pipelineStr, pipelineDevice)
} else {
pipelineStr = fmt.Sprintf(audioSrc+"avenc_g722"+pipelineStr, pipelineDevice)
}
case webrtc.PCMU:
// https://gstreamer.freedesktop.org/documentation/mulaw/mulawenc.html?gi-language=c
// gstreamer1.0-plugins-good
// audio/x-raw, rate=8000 ! mulawenc
pipelineStr = pipelineSrc + " ! audio/x-raw, rate=8000 ! mulawenc ! " + pipelineStr
clockRate = pcmClockRate
if err := CheckPlugins([]string{"pulseaudio", "mulaw"}); err != nil {
return nil, err
}
clockRate = pcmClockRate
if pipelineSrc != "" {
pipelineStr = fmt.Sprintf(pipelineSrc+pipelineStr, pipelineDevice)
} else {
pipelineStr = fmt.Sprintf(audioSrc+"audio/x-raw, rate=8000 ! mulawenc"+pipelineStr, pipelineDevice)
}
case webrtc.PCMA:
// https://gstreamer.freedesktop.org/documentation/alaw/alawenc.html?gi-language=c
// gstreamer1.0-plugins-good
// audio/x-raw, rate=8000 ! alawenc
pipelineStr = pipelineSrc + " ! audio/x-raw, rate=8000 ! alawenc ! " + pipelineStr
clockRate = pcmClockRate
if err := CheckPlugins([]string{"pulseaudio", "alaw"}); err != nil {
return nil, err
}
clockRate = pcmClockRate
if pipelineSrc != "" {
pipelineStr = fmt.Sprintf(pipelineSrc+pipelineStr, pipelineDevice)
} else {
pipelineStr = fmt.Sprintf(audioSrc+"audio/x-raw, rate=8000 ! alawenc"+pipelineStr, pipelineDevice)
}
default:
return nil, errors.Errorf("unknown video codec %s", codecName)
}
@ -169,16 +199,17 @@ func CreatePipeline(codecName string, pipelineSrc string) (*Pipeline, error) {
pipelinesLock.Lock()
defer pipelinesLock.Unlock()
pipeline := &Pipeline{
p := &Pipeline{
Pipeline: C.gstreamer_send_create_pipeline(pipelineStrUnsafe),
Sample: make(chan types.Sample),
CodecName: codecName,
ClockRate: clockRate,
Src: pipelineStr,
id: len(pipelines),
}
pipelines[pipeline.id] = pipeline
return pipeline, nil
pipelines[p.id] = p
return p, nil
}
// Start starts the GStreamer Pipeline

View File

@ -1,33 +1,53 @@
package event
const SYSTEM_DISCONNECT = "system/disconnect"
const (
SYSTEM_DISCONNECT = "system/disconnect"
)
const SIGNAL_ANSWER = "signal/answer"
const SIGNAL_PROVIDE = "signal/provide"
const (
SIGNAL_ANSWER = "signal/answer"
SIGNAL_PROVIDE = "signal/provide"
)
const IDENTITY_PROVIDE = "identity/provide"
const IDENTITY_DETAILS = "identity/details"
const (
IDENTITY_PROVIDE = "identity/provide"
IDENTITY_DETAILS = "identity/details"
)
const MEMBER_LIST = "member/list"
const MEMBER_CONNECTED = "member/connected"
const MEMBER_DISCONNECTED = "member/disconnected"
const (
MEMBER_LIST = "member/list"
MEMBER_CONNECTED = "member/connected"
MEMBER_DISCONNECTED = "member/disconnected"
)
const CONTROL_LOCKED = "control/locked"
const CONTROL_RELEASE = "control/release"
const CONTROL_REQUEST = "control/request"
const CONTROL_REQUESTING = "control/requesting"
const CONTROL_GIVE = "control/give"
const CONTROL_CLIPBOARD = "control/clipboard"
const (
CONTROL_LOCKED = "control/locked"
CONTROL_RELEASE = "control/release"
CONTROL_REQUEST = "control/request"
CONTROL_REQUESTING = "control/requesting"
CONTROL_GIVE = "control/give"
CONTROL_CLIPBOARD = "control/clipboard"
)
const CHAT_MESSAGE = "chat/message"
const CHAT_EMOTE = "chat/emote"
const (
CHAT_MESSAGE = "chat/message"
CHAT_EMOTE = "chat/emote"
)
const ADMIN_BAN = "admin/ban"
const ADMIN_KICK = "admin/kick"
const ADMIN_LOCK = "admin/lock"
const ADMIN_MUTE = "admin/mute"
const ADMIN_UNLOCK = "admin/unlock"
const ADMIN_UNMUTE = "admin/unmute"
const ADMIN_CONTROL = "admin/control"
const ADMIN_RELEASE = "admin/release"
const ADMIN_GIVE = "admin/give"
const (
SCREEN_CONFIGURATIONS = "screen/configurations"
SCREEN_RESOLUTION = "screen/resolution"
SCREEN_SET = "screen/set"
)
const (
ADMIN_BAN = "admin/ban"
ADMIN_KICK = "admin/kick"
ADMIN_LOCK = "admin/lock"
ADMIN_MUTE = "admin/mute"
ADMIN_UNLOCK = "admin/unlock"
ADMIN_UNMUTE = "admin/unmute"
ADMIN_CONTROL = "admin/control"
ADMIN_RELEASE = "admin/release"
ADMIN_GIVE = "admin/give"
)

View File

@ -90,3 +90,16 @@ type AdminTarget struct {
Target string `json:"target"`
ID string `json:"id"`
}
type ScreenResolution struct {
Event string `json:"event"`
ID string `json:"id,omitempty"`
Width int `json:"width"`
Height int `json:"height"`
Rate int `json:"rate"`
}
type ScreenConfigurations struct {
Event string `json:"event"`
Configurations map[int]types.ScreenConfiguration `json:"configurations"`
}

View File

@ -9,6 +9,7 @@ type WebRTCManager interface {
Start()
Shutdown() error
CreatePeer(id string, sdp string) (string, Peer, error)
ChangeScreenSize(width int, height int, rate int) error
}
type Peer interface {

View File

@ -0,0 +1,13 @@
package types
type ScreenSize struct {
Width int `json:"width"`
Height int `json:"height"`
Rate int16 `json:"rate"`
}
type ScreenConfiguration struct {
Width int `json:"width"`
Height int `json:"height"`
Rates map[int]int16 `json:"rates"`
}

View File

@ -6,7 +6,7 @@ import (
"strconv"
"github.com/pion/webrtc/v2"
"n.eko.moe/neko/internal/hid"
"n.eko.moe/neko/internal/xorg"
)
const OP_MOVE = 0x01
@ -63,7 +63,7 @@ func (m *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) error {
return err
}
hid.Move(int(payload.X), int(payload.Y))
xorg.Move(int(payload.X), int(payload.Y))
break
case OP_SCROLL:
payload := &PayloadScroll{}
@ -77,7 +77,7 @@ func (m *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) error {
Str("y", strconv.Itoa(int(payload.Y))).
Msg("scroll")
hid.Scroll(int(payload.X), int(payload.Y))
xorg.Scroll(int(payload.X), int(payload.Y))
break
case OP_KEY_DOWN:
payload := &PayloadKey{}
@ -86,7 +86,7 @@ func (m *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) error {
}
if payload.Key < 8 {
button, err := hid.ButtonDown(int(payload.Key))
button, err := xorg.ButtonDown(int(payload.Key))
if err != nil {
m.logger.Warn().Err(err).Msg("key down failed")
return nil
@ -94,7 +94,7 @@ func (m *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) error {
m.logger.Debug().Msgf("button down %s(%d)", button.Name, payload.Key)
} else {
key, err := hid.KeyDown(int(payload.Key))
key, err := xorg.KeyDown(int(payload.Key))
if err != nil {
m.logger.Warn().Err(err).Msg("key down failed")
return nil
@ -112,7 +112,7 @@ func (m *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) error {
}
if payload.Key < 8 {
button, err := hid.ButtonUp(int(payload.Key))
button, err := xorg.ButtonUp(int(payload.Key))
if err != nil {
m.logger.Warn().Err(err).Msg("button up failed")
return nil
@ -120,7 +120,7 @@ func (m *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) error {
m.logger.Debug().Msgf("button up %s(%d)", button.Name, payload.Key)
} else {
key, err := hid.KeyUp(int(payload.Key))
key, err := xorg.KeyUp(int(payload.Key))
if err != nil {
m.logger.Warn().Err(err).Msg("keyup failed")
return nil

View File

@ -2,6 +2,7 @@ package webrtc
import (
"fmt"
"strings"
"time"
"github.com/pion/webrtc/v2"
@ -9,9 +10,9 @@ import (
"github.com/rs/zerolog/log"
"n.eko.moe/neko/internal/gst"
"n.eko.moe/neko/internal/hid"
"n.eko.moe/neko/internal/types"
"n.eko.moe/neko/internal/types/config"
"n.eko.moe/neko/internal/xorg"
)
func New(sessions types.SessionManager, config *config.WebRTC) *WebRTCManager {
@ -53,11 +54,12 @@ type WebRTCManager struct {
}
func (m *WebRTCManager) Start() {
hid.Display(m.config.Display)
xorg.Display(m.config.Display)
videoPipeline, err := gst.CreatePipeline(
m.config.VideoCodec,
fmt.Sprintf("ximagesrc xid=%s show-pointer=true use-damage=false ! video/x-raw,framerate=30/1 ! videoconvert ! queue", m.config.Display),
m.config.Display,
m.config.VideoParams,
)
if err != nil {
@ -66,7 +68,8 @@ func (m *WebRTCManager) Start() {
audioPipeline, err := gst.CreatePipeline(
m.config.AudioCodec,
fmt.Sprintf("pulsesrc device=%s ! audioconvert", m.config.Device),
m.config.Device,
m.config.AudioParams,
)
if err != nil {
@ -88,22 +91,22 @@ func (m *WebRTCManager) Start() {
select {
case <-m.shutdown:
return
case sample := <-videoPipeline.Sample:
case sample := <-m.videoPipeline.Sample:
if err := m.sessions.WriteVideoSample(sample); err != nil {
m.logger.Warn().Err(err).Msg("video pipeline failed to write")
}
case sample := <-audioPipeline.Sample:
case sample := <-m.audioPipeline.Sample:
if err := m.sessions.WriteAudioSample(sample); err != nil {
m.logger.Warn().Err(err).Msg("audio pipeline failed to write")
}
case <-m.cleanup.C:
hid.Check(time.Second * 10)
xorg.CheckKeys(time.Second * 10)
}
}
}()
m.sessions.OnHostCleared(func(id string) {
hid.Reset()
xorg.ResetKeys()
})
m.sessions.OnCreated(func(id string, session types.Session) {
@ -120,6 +123,10 @@ func (m *WebRTCManager) Start() {
Str("video_codec", m.config.VideoCodec).
Str("audio_device", m.config.Device).
Str("audio_codec", m.config.AudioCodec).
Str("ephemeral_port_range", fmt.Sprintf("%d-%d", m.config.EphemeralMin, m.config.EphemeralMax)).
Str("nat_ips", strings.Join(m.config.NAT1To1IPs, ",")).
Str("audio_pipeline_src", audioPipeline.Src).
Str("video_pipeline_src", videoPipeline.Src).
Msgf("webrtc streaming")
}
@ -180,7 +187,7 @@ func (m *WebRTCManager) CreatePeer(id string, sdp string) (string, types.Peer, e
return "", nil, err
}
// set remote description
// Set remote description
connection.SetRemoteDescription(description)
answer, err := connection.CreateAnswer(nil)
@ -222,3 +229,26 @@ func (m *WebRTCManager) CreatePeer(id string, sdp string) (string, types.Peer, e
connection: connection,
}, nil
}
func (m *WebRTCManager) ChangeScreenSize(width int, height int, rate int) error {
m.videoPipeline.Stop()
if err := xorg.ChangeScreenSize(width, height, rate); err != nil {
return err
}
videoPipeline, err := gst.CreatePipeline(
m.config.VideoCodec,
m.config.Display,
m.config.VideoParams,
)
if err != nil {
m.logger.Panic().Err(err).Msg("unable to create new video pipeline")
}
m.videoPipeline = videoPipeline
m.videoPipeline.Start()
return nil
}

View File

@ -1,10 +1,10 @@
package websocket
import (
"n.eko.moe/neko/internal/hid"
"n.eko.moe/neko/internal/types"
"n.eko.moe/neko/internal/types/event"
"n.eko.moe/neko/internal/types/message"
"n.eko.moe/neko/internal/xorg"
)
func (h *MessageHandler) controlRelease(id string, session types.Session) error {
@ -113,6 +113,6 @@ func (h *MessageHandler) controlClipboard(id string, session types.Session, payl
return nil
}
hid.WriteClipboard(payload.Text)
xorg.WriteClipboard(payload.Text)
return nil
}

View File

@ -103,6 +103,18 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
return h.chatEmote(id, session, payload)
}), "%s failed", header.Event)
// Screen Events
case event.SCREEN_RESOLUTION:
return errors.Wrapf(h.screenResolution(id, session), "%s failed", header.Event)
case event.SCREEN_CONFIGURATIONS:
return errors.Wrapf(h.screenConfigurations(id, session), "%s failed", header.Event)
case event.SCREEN_SET:
payload := &message.ScreenResolution{}
return errors.Wrapf(
utils.Unmarshal(payload, raw, func() error {
return h.screenSet(id, session, payload)
}), "%s failed", header.Event)
// Admin Events
case event.ADMIN_LOCK:
return errors.Wrapf(h.adminLock(id, session), "%s failed", header.Event)

View File

@ -0,0 +1,67 @@
package websocket
import (
"n.eko.moe/neko/internal/types"
"n.eko.moe/neko/internal/types/event"
"n.eko.moe/neko/internal/types/message"
"n.eko.moe/neko/internal/xorg"
)
func (h *MessageHandler) screenSet(id string, session types.Session, payload *message.ScreenResolution) error {
if !session.Admin() {
h.logger.Debug().Msg("user not admin")
return nil
}
if err := h.webrtc.ChangeScreenSize(payload.Width, payload.Height, payload.Rate); err != nil {
h.logger.Warn().Err(err).Msgf("unable to change screen size")
return err
}
if err := h.sessions.Brodcast(
message.ScreenResolution{
Event: event.SCREEN_RESOLUTION,
ID: id,
Width: payload.Width,
Height: payload.Height,
Rate: payload.Rate,
}, nil); err != nil {
h.logger.Warn().Err(err).Msgf("brodcasting event %s has failed", event.SCREEN_RESOLUTION)
return err
}
return nil
}
func (h *MessageHandler) screenResolution(id string, session types.Session) error {
if size := xorg.GetScreenSize(); size != nil {
if err := session.Send(message.ScreenResolution{
Event: event.SCREEN_RESOLUTION,
Width: size.Width,
Height: size.Height,
Rate: int(size.Rate),
}); err != nil {
h.logger.Warn().Err(err).Msgf("sending event %s has failed", event.SCREEN_RESOLUTION)
return err
}
}
return nil
}
func (h *MessageHandler) screenConfigurations(id string, session types.Session) error {
if !session.Admin() {
h.logger.Debug().Msg("user not admin")
return nil
}
if err := session.Send(message.ScreenConfigurations{
Event: event.SCREEN_CONFIGURATIONS,
Configurations: xorg.ScreenConfigurations,
}); err != nil {
h.logger.Warn().Err(err).Msgf("sending event %s has failed", event.SCREEN_CONFIGURATIONS)
return err
}
return nil
}

View File

@ -14,6 +14,18 @@ func (h *MessageHandler) SessionCreated(id string, session types.Session) error
return err
}
// send screen current resolution
if err := h.screenResolution(id, session); err != nil {
return err
}
if session.Admin() {
// send screen configurations
if err := h.screenConfigurations(id, session); err != nil {
return err
}
}
return nil
}

View File

@ -9,12 +9,12 @@ import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"n.eko.moe/neko/internal/hid"
"n.eko.moe/neko/internal/types"
"n.eko.moe/neko/internal/types/config"
"n.eko.moe/neko/internal/types/event"
"n.eko.moe/neko/internal/types/message"
"n.eko.moe/neko/internal/utils"
"n.eko.moe/neko/internal/xorg"
)
func New(sessions types.SessionManager, webrtc types.WebRTCManager, conf *config.WebSocket) *WebSocketHandler {
@ -81,7 +81,7 @@ func (ws *WebSocketHandler) Start() error {
ws.logger.Info().Msg("shutdown")
}()
current := hid.ReadClipboard()
current := xorg.ReadClipboard()
for {
select {
@ -89,7 +89,7 @@ func (ws *WebSocketHandler) Start() error {
return
default:
if ws.sessions.HasHost() {
text := hid.ReadClipboard()
text := xorg.ReadClipboard()
if text != current {
session, ok := ws.sessions.GetHost()
if ok {

View File

@ -1,4 +1,4 @@
#include "hid.h"
#include "xorg.h"
static clipboard_c *CLIPBOARD = NULL;
static Display *DISPLAY = NULL;
@ -9,7 +9,7 @@ static int DIRTY = 0;
Display *getXDisplay(void) {
/* Close the display if displayName has changed */
if (DIRTY) {
closeXDisplay();
XDisplayClose();
DIRTY = 0;
}
@ -25,7 +25,7 @@ Display *getXDisplay(void) {
if (DISPLAY == NULL) {
fputs("Could not open main display\n", stderr);
} else if (!REGISTERED) {
atexit(&closeXDisplay);
atexit(&XDisplayClose);
REGISTERED = 1;
}
}
@ -40,14 +40,14 @@ clipboard_c *getClipboard(void) {
return CLIPBOARD;
}
void closeXDisplay(void) {
void XDisplayClose(void) {
if (DISPLAY != NULL) {
XCloseDisplay(DISPLAY);
DISPLAY = NULL;
}
}
void setXDisplay(char *input) {
void XDisplaySet(char *input) {
NAME = strdup(input);
DIRTY = 1;
}
@ -110,3 +110,41 @@ char *XClipboardGet() {
clipboard_c *cb = getClipboard();
return clipboard_text_ex(cb, NULL, 0);
}
void XGetScreenConfigurations() {
Display *display = getXDisplay();
Window root = RootWindow(display, 0);
XRRScreenSize *xrrs;
int num_sizes;
xrrs = XRRSizes(display, 0, &num_sizes);
for(int i = 0; i < num_sizes; i ++) {
short *rates;
int num_rates;
goCreateScreenSize(i, xrrs[i].width, xrrs[i].height, xrrs[i].mwidth, xrrs[i].mheight);
rates = XRRRates(display, 0, i, &num_rates);
for (int j = 0; j < num_rates; j ++) {
goSetScreenRates(i, j, rates[j]);
}
}
}
void XSetScreenConfiguration(int index, short rate) {
Display *display = getXDisplay();
Window root = RootWindow(display, 0);
XRRSetScreenConfigAndRate(display, XRRGetScreenInfo(display, root), root, index, RR_Rotate_0, rate, CurrentTime);
}
int XGetScreenSize() {
Display *display = getXDisplay();
XRRScreenConfiguration *conf = XRRGetScreenInfo(display, RootWindow(display, 0));
Rotation original_rotation;
return XRRConfigCurrentConfiguration(conf, &original_rotation);
}
short XGetScreenRate() {
Display *display = getXDisplay();
XRRScreenConfiguration *conf = XRRGetScreenInfo(display, RootWindow(display, 0));
return XRRConfigCurrentRate(conf);
}

View File

@ -4,13 +4,13 @@
// pretty sure this *isn't* thread safe either.... /shrug
// if you know a better way to get this done *please* make a pr <3
package hid
package xorg
/*
#cgo linux CFLAGS: -I/usr/src
#cgo linux LDFLAGS: -L/usr/src -lX11 -lXtst -lclipboard
#cgo linux LDFLAGS: -L/usr/src -lX11 -lXtst -lXrandr -lclipboard
#include "hid.h"
#include "xorg.h"
*/
import "C"
@ -18,10 +18,14 @@ import (
"fmt"
"sync"
"time"
"unsafe"
"n.eko.moe/neko/internal/hid/keycode"
"n.eko.moe/neko/internal/types"
"n.eko.moe/neko/internal/xorg/keycode"
)
var ScreenConfigurations = make(map[int]types.ScreenConfiguration)
var debounce = make(map[int]time.Time)
var buttons = make(map[int]keycode.Button)
var keys = make(map[int]keycode.Key)
@ -135,13 +139,18 @@ func init() {
buttons[keycode.SCROLL_DOWN_BUTTON.Code] = keycode.SCROLL_DOWN_BUTTON
buttons[keycode.SCROLL_LEFT_BUTTON.Code] = keycode.SCROLL_LEFT_BUTTON
buttons[keycode.SCROLL_RIGHT_BUTTON.Code] = keycode.SCROLL_RIGHT_BUTTON
C.XGetScreenConfigurations()
}
func Display(display string) {
mu.Lock()
defer mu.Unlock()
C.setXDisplay(C.CString(display))
displayUnsafe := C.CString(display)
defer C.free(unsafe.Pointer(displayUnsafe))
C.XDisplaySet(displayUnsafe)
}
func Move(x, y int) {
@ -238,17 +247,23 @@ func ReadClipboard() string {
mu.Lock()
defer mu.Unlock()
return C.GoString(C.XClipboardGet())
clipboardUnsafe := C.XClipboardGet()
defer C.free(unsafe.Pointer(clipboardUnsafe))
return C.GoString(clipboardUnsafe)
}
func WriteClipboard(data string) {
mu.Lock()
defer mu.Unlock()
C.XClipboardSet(C.CString(data))
clipboardUnsafe := C.CString(data)
defer C.free(unsafe.Pointer(clipboardUnsafe))
C.XClipboardSet(clipboardUnsafe)
}
func Reset() {
func ResetKeys() {
for key := range debounce {
if key < 8 {
ButtonUp(key)
@ -260,7 +275,7 @@ func Reset() {
}
}
func Check(duration time.Duration) {
func CheckKeys(duration time.Duration) {
t := time.Now()
for key, start := range debounce {
if t.Sub(start) < duration {
@ -276,3 +291,53 @@ func Check(duration time.Duration) {
delete(debounce, key)
}
}
func ChangeScreenSize(width int, height int, rate int) error {
mu.Lock()
defer mu.Unlock()
for index, size := range ScreenConfigurations {
if size.Width == width && size.Height == height {
for _, srate := range size.Rates {
if int16(rate) == srate {
C.XSetScreenConfiguration(C.int(index), C.short(srate))
return nil
}
}
}
}
return fmt.Errorf("unknown configuration")
}
func GetScreenSize() *types.ScreenSize {
mu.Lock()
defer mu.Unlock()
index := int(C.XGetScreenSize())
rate := int16(C.XGetScreenRate())
if conf, ok := ScreenConfigurations[index]; ok {
return &types.ScreenSize{
Width: conf.Width,
Height: conf.Height,
Rate: rate,
}
}
return nil
}
//export goCreateScreenSize
func goCreateScreenSize(index C.int, width C.int, height C.int, mwidth C.int, mheight C.int) {
ScreenConfigurations[int(index)] = types.ScreenConfiguration{
Width: int(width),
Height: int(height),
Rates: make(map[int]int16),
}
}
//export goSetScreenRates
func goSetScreenRates(index C.int, rate_index C.int, rate C.short) {
ScreenConfigurations[int(index)].Rates[int(rate_index)] = int16(rate)
}

View File

@ -4,6 +4,7 @@
#define XDISPLAY_H
#include <X11/Xlib.h>
#include <X11/extensions/Xrandr.h>
#include <X11/extensions/XTest.h>
#include <libclipboard.h>
#include <stdint.h>
@ -11,6 +12,9 @@
#include <stdio.h> /* For fputs() */
#include <string.h> /* For strdup() */
extern void goCreateScreenSize(int index, int width, int height, int mwidth, int mheight);
extern void goSetScreenRates(int index, int rate_index, short rate);
/* Returns the main display, closed either on exit or when closeMainDisplay()
* is invoked. This removes a bit of the overhead of calling XOpenDisplay() &
* XCloseDisplay() everytime the main display needs to be used.
@ -19,14 +23,20 @@
Display *getXDisplay(void);
clipboard_c *getClipboard(void);
void XClipboardSet(char *src);
char *XClipboardGet();
void XMove(int x, int y);
void XScroll(int x, int y);
void XButton(unsigned int button, int down);
void XKey(unsigned long key, int down);
void closeXDisplay(void);
void setXDisplay(char *input);
void XClipboardSet(char *src);
char *XClipboardGet();
void XGetScreenConfigurations();
void XSetScreenConfiguration(int index, short rate);
int XGetScreenSize();
short XGetScreenRate();
void XDisplayClose(void);
void XDisplaySet(char *input);
#endif