Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/watcher: implement dynamic library loading for X11, XRes and Xss #81

Merged
merged 1 commit into from
May 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ The service can be stopped with `systemctl --user stop dex.service`.

Interaction with Stream Deck devices depends on github.com/sstallion/go-hid. This package makes use of [non-Go dependencies](https://github.com/libusb/hidapi/blob/master/BUILD.md#prerequisites).

The `watcher` module depends on libxss and libxres on linux (libxss-dev and libxres-dev in deb-based distributions). Testing the `watcher` module uses gioui.org, and so [its dependencies](https://gioui.org/doc/install) must be provided if testing the module.
The `watcher` module depends on Xlib, libXss and libXRes on linux (libx11-dev, libxss1/libxss-dev and libxres1/libxres-dev in deb-based distributions). Xlib headers are required during compilation, but the libraries may be absent with reduced or absent functionality. Testing the `watcher` module uses gioui.org, and so [its dependencies](https://gioui.org/doc/install) must be provided if testing the module.

## Linux udev rules

Expand Down
156 changes: 156 additions & 0 deletions cmd/watcher/XRes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
Copyright (c) 2002 XFree86 Inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is fur-
nished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT-
NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CON-
NECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the name of the XFree86 Project shall not
be used in advertising or otherwise to promote the sale, use or other deal-
ings in this Software without prior written authorization from the XFree86
Project.
*/

#ifndef _XRES_H
#define _XRES_H

#include <X11/Xfuncproto.h>

/* v1.0 */

typedef struct {
XID resource_base;
XID resource_mask;
} XResClient;

typedef struct {
Atom resource_type;
unsigned int count;
} XResType;

/* v1.2 */

typedef enum {
XRES_CLIENT_ID_XID,
XRES_CLIENT_ID_PID,
XRES_CLIENT_ID_NR
} XResClientIdType;

typedef enum {
XRES_CLIENT_ID_XID_MASK = 1 << XRES_CLIENT_ID_XID,
XRES_CLIENT_ID_PID_MASK = 1 << XRES_CLIENT_ID_PID
} XResClientIdMask;

typedef struct {
XID client;
unsigned int mask;
} XResClientIdSpec;

typedef struct {
XResClientIdSpec spec;
long length;
void *value;
} XResClientIdValue;

typedef struct {
XID resource;
Atom type;
} XResResourceIdSpec;

typedef struct {
XResResourceIdSpec spec;
long bytes;
long ref_count;
long use_count;
} XResResourceSizeSpec;

typedef struct {
XResResourceSizeSpec size;
long num_cross_references;
XResResourceSizeSpec *cross_references;
} XResResourceSizeValue;

_XFUNCPROTOBEGIN

/* v1.0 */

Bool XResQueryExtension (
Display *dpy,
int *event_base_return,
int *error_base_return
);

Status XResQueryVersion (
Display *dpy,
int *major_version_return,
int *minor_version_return
);

Status XResQueryClients (
Display *dpy,
int *num_clients,
XResClient **clients
);

Status XResQueryClientResources (
Display *dpy,
XID xid,
int *num_types,
XResType **types
);

Status XResQueryClientPixmapBytes (
Display *dpy,
XID xid,
unsigned long *bytes
);

/* v1.2 */

Status XResQueryClientIds (
Display *dpy,
long num_specs,
XResClientIdSpec *client_specs, /* in */
long *num_ids, /* out */
XResClientIdValue **client_ids /* out */
);

XResClientIdType XResGetClientIdType(XResClientIdValue* value);

/* return -1 if no pid associated to the value */
pid_t XResGetClientPid(XResClientIdValue* value);

void XResClientIdsDestroy (
long num_ids,
XResClientIdValue *client_ids
);

Status XResQueryResourceBytes (
Display *dpy,
XID client,
long num_specs,
XResResourceIdSpec *resource_specs, /* in */
long *num_sizes, /* out */
XResResourceSizeValue **sizes /* out */
);

void XResResourceSizeValuesDestroy (
long num_sizes,
XResResourceSizeValue *sizes
);

_XFUNCPROTOEND

#endif /* _XRES_H */
27 changes: 27 additions & 0 deletions cmd/watcher/dl/dlopen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright ©2024 Dan Kortschak. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package dl implements dlopen and related functionality.
package dl

import (
"errors"
"unsafe"
)

var ErrNotFound = errors.New("so lib not found")

// Lib represents an open handle to a dynamically loaded library.
type Lib struct {
handle unsafe.Pointer
name string
}

// Name returns the resolved name of the library.
func (l *Lib) Name() string { return l.name }

// Error is a dlerror error message.
type Error string

func (e Error) Error() string { return string(e) }
39 changes: 39 additions & 0 deletions cmd/watcher/dl/dlopen_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright ©2024 Dan Kortschak. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build !unix

package dl

import (
"errors"
"unsafe"
)

const (
RTLD_LAZY = 0
RTLD_NOW = 0
RTLD_GLOBAL = 0
RTLD_LOCAL = 0
RTLD_NODELETE = 0
RTLD_NOLOAD = 0
RTLD_DEEPBIND = 0
)

var errNotImplemented = errors.New("not implemented")

// Open is not implemented on this platform.
func Open(_ int, _ ...string) (*Lib, error) {
return nil, errNotImplemented
}

// Symbol is not implemented on this platform.
func (l *Lib) Symbol(name string) (unsafe.Pointer, error) {
return nil, errNotImplemented
}

// Close is not implemented on this platform.
func (l *Lib) Close() error {
return errNotImplemented
}
77 changes: 77 additions & 0 deletions cmd/watcher/dl/dlopen_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright ©2024 Dan Kortschak. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build unix

package dl

/*
#cgo LDFLAGS: -ldl
#include <stdlib.h>
#include <dlfcn.h>
*/
import "C"

import (
"fmt"
"unsafe"
)

const (
RTLD_LAZY = int(C.RTLD_LAZY)
RTLD_NOW = int(C.RTLD_NOW)
RTLD_GLOBAL = int(C.RTLD_GLOBAL)
RTLD_LOCAL = int(C.RTLD_LOCAL)
RTLD_NODELETE = int(C.RTLD_NODELETE)
RTLD_NOLOAD = int(C.RTLD_NOLOAD)
RTLD_DEEPBIND = int(C.RTLD_DEEPBIND)
)

// Open opens the dynamic library corresponding to the first found library name
// in names. See man 3 dlopen for details.
func Open(flags int, names ...string) (*Lib, error) {
for _, n := range names {
filename := C.CString(n)
h := C.dlopen(filename, C.int(flags))
C.free(unsafe.Pointer(filename))
if h != nil {
return &Lib{
handle: h,
name: n,
}, nil
}
}
return nil, fmt.Errorf("%w: %s", ErrNotFound, names)
}

// Symbol takes a symbol name and returns a pointer to the symbol.
func (l *Lib) Symbol(name string) (unsafe.Pointer, error) {
C.dlerror()

sym := C.CString(name)
s := C.dlsym(l.handle, sym)
C.free(unsafe.Pointer(sym))
dlErrMsg := C.dlerror()
if dlErrMsg != nil {
return nil, fmt.Errorf("could not find %s: %w", name, Error(C.GoString(dlErrMsg)))
}
return s, nil
}

// Close closes the receiver, unloading the library. Symbols must not be used
// after Close has been called.
func (l *Lib) Close() error {
if l == nil || l.handle == nil {
return nil
}
C.dlerror()

C.dlclose(l.handle)
l.handle = nil
dlErrMsg := C.dlerror()
if dlErrMsg != nil {
return fmt.Errorf("error closing: %w", Error(C.GoString(dlErrMsg)))
}
return nil
}
Loading
Loading