feat(factory): 内容工厂与链接处理

- 第三栏内容工厂:按拖入类型展示动作,动作按钮本身可接收 drop
- 链接分类:http(s) 按扩展名判定图片/音视频/文档/压缩包/网页
- http 链接动作:打开、下载、另存为、复制、Markdown、复制文件名、待办、问小埃
- 非标准 scheme:尝试打开、复制;thunder 解析 base64 真实地址,magnet 解析 dn
- 文字动作:ICU 分词、正则提取链接、朗读(spd-say)、复制、待办
- 文件/图片动作:打开、提取文字(占位)、生成描述、保存图片、复制路径、待办
- Downloader:异步下载到 XDG 下载目录 / 另存为,面板 toast 提示
- TodoModel:持久化到 todo.json,供「添加到待办」使用
- 无头自测 gxde-ai-see-selftest:链接分类/分词/提取链接/动作匹配
- 合成器 portal 自测脚本 tests/portal(虚拟指针 + GTK 源/目标)
This commit is contained in:
2026-10-30 20:09:01 +08:00
parent 68d3eb1a24
commit 3a73a8c5d1
26 changed files with 2822 additions and 6 deletions
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
# Build the wlr-virtual-pointer test driver.
set -e
cd "$(dirname "$0")"
cc -O2 -Wall pointer.c virtual-pointer-protocol.c -o pointer $(pkg-config --cflags --libs wayland-client)
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2026 GXDE OS Team
# SPDX-License-Identifier: GPL-3.0-or-later
"""GTK3 drag source used by the portal integration test."""
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gdk, Gtk # noqa: E402
class Source(Gtk.Window):
def __init__(self):
super().__init__(title="PortalDragSource")
self.set_default_size(420, 280)
self.set_position(Gtk.WindowPosition.CENTER)
label = Gtk.Label()
label.set_markup("<span size='x-large'>DRAG ME</span>\n\nportal test payload")
label.set_hexpand(True)
label.set_vexpand(True)
box = Gtk.EventBox()
box.add(label)
self.add(box)
box.add_events(
Gdk.EventMask.BUTTON_PRESS_MASK
| Gdk.EventMask.BUTTON_RELEASE_MASK
| Gdk.EventMask.POINTER_MOTION_MASK
)
box.connect("button-press-event", lambda *args: print("PRESS", flush=True))
box.connect("button-release-event", lambda *args: print("RELEASE", flush=True))
box.connect("motion-notify-event", lambda *args: print("MOTION", flush=True))
box.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
box.drag_source_add_text_targets()
box.connect("drag-data-get", self.on_drag_data_get)
box.connect("drag-begin", lambda *args: print("DRAG-BEGIN", flush=True))
box.connect("drag-end", lambda *args: print("DRAG-END", flush=True))
self.connect("destroy", Gtk.main_quit)
self.show_all()
def on_drag_data_get(self, widget, context, data, info, time):
data.set_text("portal-payload-42", -1)
Source()
Gtk.main()
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2026 GXDE OS Team
# SPDX-License-Identifier: GPL-3.0-or-later
"""GTK3 drop target used by the portal integration test.
GtkTextView accepts text drops out of the box; every insertion is printed as
``DROP <text> at <offset>`` so the orchestrator can assert that the *native*
drop landed in this window.
"""
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa: E402
class Target(Gtk.Window):
def __init__(self):
super().__init__(title="PortalDropTarget")
self.set_default_size(420, 280)
self.set_position(Gtk.WindowPosition.CENTER)
view = Gtk.TextView()
view.set_wrap_mode(Gtk.WrapMode.WORD)
self.buffer = view.get_buffer()
self.buffer.connect("insert-text", self.on_insert)
scroller = Gtk.ScrolledWindow()
scroller.add(view)
self.add(scroller)
self.connect("destroy", Gtk.main_quit)
self.show_all()
def on_insert(self, buffer, location, text, length):
print("DROP", repr(text), "at", location.get_offset(), flush=True)
Target()
Gtk.main()
+234
View File
@@ -0,0 +1,234 @@
// SPDX-FileCopyrightText: 2026 GXDE OS Team
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Minimal wlr-virtual-pointer driver used by the portal integration test.
// Reads commands from stdin, one per line:
// move <x> <y> absolute motion in output coordinates
// press left button down
// release left button up
// sleep <ms>
// size print "size <w> <h>"
// quit
// After each command it prints "ok <line>" so the orchestrator can sync.
#define _POSIX_C_SOURCE 200809L
#include <linux/input-event-codes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <wayland-client.h>
#include "virtual-pointer-client.h"
struct state {
struct wl_display *display;
struct wl_seat *seat;
struct zwlr_virtual_pointer_manager_v1 *manager;
struct zwlr_virtual_pointer_v1 *pointer;
uint32_t width;
uint32_t height;
};
static uint32_t now_ms(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint32_t)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
}
static void output_geometry(void *data, struct wl_output *output, int32_t x, int32_t y,
int32_t physical_w, int32_t physical_h, int32_t subpixel,
const char *make, const char *model, int32_t transform)
{
(void)data;
(void)output;
(void)x;
(void)y;
(void)physical_w;
(void)physical_h;
(void)subpixel;
(void)make;
(void)model;
(void)transform;
}
static void output_mode(void *data, struct wl_output *output, uint32_t flags, int32_t width,
int32_t height, int32_t refresh)
{
(void)output;
(void)flags;
(void)refresh;
struct state *state = data;
if (width > 0 && height > 0) {
state->width = (uint32_t)width;
state->height = (uint32_t)height;
}
}
static void output_done(void *data, struct wl_output *output)
{
(void)data;
(void)output;
}
static void output_scale(void *data, struct wl_output *output, int32_t factor)
{
(void)data;
(void)output;
(void)factor;
}
static void output_name(void *data, struct wl_output *output, const char *name)
{
(void)data;
(void)output;
(void)name;
}
static void output_description(void *data, struct wl_output *output, const char *description)
{
(void)data;
(void)output;
(void)description;
}
static const struct wl_output_listener output_listener = {
.geometry = output_geometry,
.mode = output_mode,
.done = output_done,
.scale = output_scale,
.name = output_name,
.description = output_description,
};
static void seat_capabilities(void *data, struct wl_seat *seat, uint32_t caps)
{
(void)data;
(void)seat;
(void)caps;
}
static void seat_name(void *data, struct wl_seat *seat, const char *name)
{
(void)data;
(void)seat;
(void)name;
}
static const struct wl_seat_listener seat_listener = {
.capabilities = seat_capabilities,
.name = seat_name,
};
static void registry_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version)
{
struct state *state = data;
if (strcmp(interface, wl_output_interface.name) == 0) {
struct wl_output *output =
wl_registry_bind(registry, name, &wl_output_interface, version < 2 ? version : 2);
wl_output_add_listener(output, &output_listener, state);
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
state->seat = wl_registry_bind(registry, name, &wl_seat_interface, version < 5 ? version : 5);
wl_seat_add_listener(state->seat, &seat_listener, state);
} else if (strcmp(interface, zwlr_virtual_pointer_manager_v1_interface.name) == 0) {
state->manager = wl_registry_bind(registry, name,
&zwlr_virtual_pointer_manager_v1_interface,
version < 2 ? version : 2);
}
}
static void registry_global_remove(void *data, struct wl_registry *registry, uint32_t name)
{
(void)data;
(void)registry;
(void)name;
}
static const struct wl_registry_listener registry_listener = {
.global = registry_global,
.global_remove = registry_global_remove,
};
static void send_motion(struct state *state, double x, double y)
{
zwlr_virtual_pointer_v1_motion_absolute(state->pointer, now_ms(), wl_fixed_from_double(x),
wl_fixed_from_double(y), state->width,
state->height);
zwlr_virtual_pointer_v1_frame(state->pointer);
wl_display_roundtrip(state->display);
}
static void send_button(struct state *state, uint32_t button, uint32_t button_state)
{
zwlr_virtual_pointer_v1_button(state->pointer, now_ms(), button, button_state);
zwlr_virtual_pointer_v1_frame(state->pointer);
wl_display_roundtrip(state->display);
}
int main(void)
{
struct state state = { 0 };
state.display = wl_display_connect(NULL);
if (!state.display) {
fprintf(stderr, "cannot connect to wayland display\n");
return 1;
}
struct wl_registry *registry = wl_display_get_registry(state.display);
wl_registry_add_listener(registry, &registry_listener, &state);
wl_display_roundtrip(state.display);
wl_display_roundtrip(state.display);
if (!state.seat || !state.manager) {
fprintf(stderr, "missing seat or virtual pointer manager\n");
return 1;
}
if (state.width == 0 || state.height == 0) {
state.width = 1280;
state.height = 720;
}
state.pointer = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(state.manager, state.seat);
if (!state.pointer) {
fprintf(stderr, "cannot create virtual pointer\n");
return 1;
}
wl_display_roundtrip(state.display);
char line[256];
while (fgets(line, sizeof(line), stdin)) {
double x = 0.0;
double y = 0.0;
int ms = 0;
if (sscanf(line, "move %lf %lf", &x, &y) == 2) {
send_motion(&state, x, y);
} else if (strncmp(line, "press", 5) == 0) {
send_button(&state, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED);
} else if (strncmp(line, "release", 7) == 0) {
send_button(&state, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED);
} else if (sscanf(line, "sleep %d", &ms) == 1) {
struct timespec delay = { ms / 1000, (long)(ms % 1000) * 1000000L };
nanosleep(&delay, NULL);
} else if (strncmp(line, "size", 4) == 0) {
printf("size %u %u\n", state.width, state.height);
} else if (strncmp(line, "quit", 4) == 0) {
break;
}
printf("ok %s", line);
fflush(stdout);
}
zwlr_virtual_pointer_v1_destroy(state.pointer);
zwlr_virtual_pointer_manager_v1_destroy(state.manager);
wl_display_disconnect(state.display);
return 0;
}
+242
View File
@@ -0,0 +1,242 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2026 GXDE OS Team
# SPDX-License-Identifier: GPL-3.0-or-later
"""End-to-end test for the GXDE portal (native drop into a window).
Run inside a private session bus:
dbus-run-session -- python3 tests/portal/run.py [compositor]
It starts a nested GXDE compositor, a GTK drag source and a GTK drop target,
drives the pointer through wlr-virtual-pointer, asks the compositor to open the
portal overview and asserts that the drop lands in the target window.
"""
import glob
import json
import os
import subprocess
import sys
import time
HERE = os.path.dirname(os.path.abspath(__file__))
RUNTIME = f"/run/user/{os.getuid()}"
PORTAL = ["org.gxde.Portal", "/org/gxde/Portal", "org.gxde.Portal"]
def log(message):
print(f"[test] {message}", flush=True)
def wait_for(predicate, timeout=20.0, interval=0.1):
deadline = time.time() + timeout
while time.time() < deadline:
value = predicate()
if value:
return value
time.sleep(interval)
return None
def portal_call(method, *args):
address = os.environ.get("DBUS_SESSION_BUS_ADDRESS", "")
cmd = ["busctl", f"--address={address}", "--json=short", "call", *PORTAL, method, *args]
out = subprocess.check_output(cmd, text=True)
return json.loads(out)
def list_windows():
data = portal_call("ListWindows")["data"]
rows = data[0] if data else []
return [
{
"uuid": row[0],
"title": row[1],
"app": row[2],
"x": row[3],
"y": row[4],
"w": row[5],
"h": row[6],
}
for row in rows
]
def layout():
data = portal_call("GetLayout")["data"]
rows = data[0] if data else []
return [
{
"uuid": row[0],
"title": row[1],
"x": row[3],
"y": row[4],
"w": row[5],
"h": row[6],
}
for row in rows
]
def find_by_title(windows, title):
for window in windows:
if window["title"] == title:
return window
return None
def main():
compositor = (
sys.argv[1]
if len(sys.argv) > 1
else os.path.expanduser("~/playground/flake-wm/build-portal/flakewm")
)
parent_display = os.environ.get("WAYLAND_DISPLAY", "wayland-0")
before = set(glob.glob(f"{RUNTIME}/wayland-*"))
env = os.environ.copy()
env["WAYLAND_DISPLAY"] = parent_display
compositor_log = open("/tmp/pt-comp.log", "wb")
compositor_proc = subprocess.Popen(
[compositor], env=env, stdout=compositor_log, stderr=subprocess.STDOUT
)
procs = [compositor_proc]
pointer = None
try:
def nested_display():
for path in set(glob.glob(f"{RUNTIME}/wayland-*")) - before:
if not path.endswith(".lock"):
return os.path.basename(path)
return None
display = wait_for(nested_display, 25.0)
assert display, "nested compositor did not create a Wayland socket"
log(f"nested display: {display}")
env["WAYLAND_DISPLAY"] = display
target_log = open("/tmp/pt-target.log", "wb")
source_log = open("/tmp/pt-source.log", "wb")
target = subprocess.Popen(
[sys.executable, f"{HERE}/drop_target.py"],
env=env, stdout=target_log, stderr=subprocess.STDOUT,
)
source = subprocess.Popen(
[sys.executable, f"{HERE}/drag_source.py"],
env=env, stdout=source_log, stderr=subprocess.STDOUT,
)
procs += [target, source]
def portal_ready():
try:
portal_call("ListWindows")
return True
except subprocess.CalledProcessError:
return False
assert wait_for(portal_ready, 20.0, 0.3), "org.gxde.Portal never appeared"
log("portal D-Bus ready")
windows = wait_for(
lambda: list_windows()
if find_by_title(list_windows(), "PortalDragSource")
and find_by_title(list_windows(), "PortalDropTarget")
else None,
20.0,
0.2,
)
assert windows, "windows did not appear"
log(f"windows: {json.dumps(windows)}")
src = find_by_title(windows, "PortalDragSource")
dst = find_by_title(windows, "PortalDropTarget")
pointer = subprocess.Popen(
[f"{HERE}/pointer"], env=env, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, text=True, bufsize=1,
)
procs.append(pointer)
def pcmd(line):
pointer.stdin.write(line + "\n")
pointer.stdin.flush()
response = pointer.stdout.readline().strip()
if not (response.startswith("ok") or response.startswith("size")):
raise RuntimeError(f"pointer failed: {response!r}")
return response
log(f"pointer {pcmd('size')}")
sx = src["x"] + src["w"] / 2
sy = src["y"] + src["h"] / 2
pcmd(f"move {sx:.0f} {sy:.0f}")
time.sleep(0.2)
pcmd("press")
time.sleep(0.2)
pcmd(f"move {sx + 60:.0f} {sy:.0f}")
time.sleep(0.8)
if "DRAG-BEGIN" not in open("/tmp/pt-source.log").read():
raise RuntimeError("GTK drag did not start; see /tmp/pt-source.log")
log("drag started")
started = portal_call("BeginPortal")["data"][0]
assert started, "BeginPortal returned false"
log("portal opened")
time.sleep(0.7) # let the overview animation settle
tiles = wait_for(lambda: layout() or None, 5.0, 0.2)
assert tiles, "portal layout is empty"
log(f"layout: {json.dumps(tiles)}")
tile = find_by_title(tiles, "PortalDropTarget") or find_by_title(tiles, "PortalDragSource")
assert tile, "target window not in the portal layout"
# If we only see the source tile, the drop still validates retargeting.
tx = tile["x"] + tile["w"] / 2
ty = tile["y"] + tile["h"] / 2
pcmd(f"move {tx:.0f} {ty:.0f}")
time.sleep(0.6)
subprocess.run(["grim", "/tmp/pt-before-drop.png"], env=env, check=False)
pcmd("release")
time.sleep(1.0)
subprocess.run(["grim", "/tmp/pt-after-drop.png"], env=env, check=False)
target_text = open("/tmp/pt-target.log").read()
log(f"target log: {target_text.strip()!r}")
assert "DROP" in target_text, "the drop did not reach the target window"
assert "portal-payload-42" in target_text, "wrong payload"
log("PASS: native drop landed in the target window")
return 0
except Exception as exc: # noqa: BLE001
log(f"FAIL: {exc}")
for path in ("/tmp/pt-comp.log", "/tmp/pt-source.log", "/tmp/pt-target.log"):
try:
content = open(path).read().strip()
except OSError:
continue
if content:
log(f"--- {path} ---\n{content[-2000:]}")
return 1
finally:
if pointer is not None:
try:
pointer.stdin.write("quit\n")
pointer.stdin.flush()
except Exception: # noqa: BLE001
pass
for proc in reversed(procs):
if proc.poll() is None:
proc.terminate()
for proc in reversed(procs):
try:
proc.wait(timeout=5)
except Exception: # noqa: BLE001
proc.kill()
if __name__ == "__main__":
sys.exit(main())
+370
View File
@@ -0,0 +1,370 @@
/* Generated by wayland-scanner 1.23.1 */
#ifndef WLR_VIRTUAL_POINTER_UNSTABLE_V1_CLIENT_PROTOCOL_H
#define WLR_VIRTUAL_POINTER_UNSTABLE_V1_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_wlr_virtual_pointer_unstable_v1 The wlr_virtual_pointer_unstable_v1 protocol
* @section page_ifaces_wlr_virtual_pointer_unstable_v1 Interfaces
* - @subpage page_iface_zwlr_virtual_pointer_v1 - virtual pointer
* - @subpage page_iface_zwlr_virtual_pointer_manager_v1 - virtual pointer manager
* @section page_copyright_wlr_virtual_pointer_unstable_v1 Copyright
* <pre>
*
* Copyright © 2019 Josef Gajdusek
*
* 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 furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) 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,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
* </pre>
*/
struct wl_output;
struct wl_seat;
struct zwlr_virtual_pointer_manager_v1;
struct zwlr_virtual_pointer_v1;
#ifndef ZWLR_VIRTUAL_POINTER_V1_INTERFACE
#define ZWLR_VIRTUAL_POINTER_V1_INTERFACE
/**
* @page page_iface_zwlr_virtual_pointer_v1 zwlr_virtual_pointer_v1
* @section page_iface_zwlr_virtual_pointer_v1_desc Description
*
* This protocol allows clients to emulate a physical pointer device. The
* requests are mostly mirror opposites of those specified in wl_pointer.
* @section page_iface_zwlr_virtual_pointer_v1_api API
* See @ref iface_zwlr_virtual_pointer_v1.
*/
/**
* @defgroup iface_zwlr_virtual_pointer_v1 The zwlr_virtual_pointer_v1 interface
*
* This protocol allows clients to emulate a physical pointer device. The
* requests are mostly mirror opposites of those specified in wl_pointer.
*/
extern const struct wl_interface zwlr_virtual_pointer_v1_interface;
#endif
#ifndef ZWLR_VIRTUAL_POINTER_MANAGER_V1_INTERFACE
#define ZWLR_VIRTUAL_POINTER_MANAGER_V1_INTERFACE
/**
* @page page_iface_zwlr_virtual_pointer_manager_v1 zwlr_virtual_pointer_manager_v1
* @section page_iface_zwlr_virtual_pointer_manager_v1_desc Description
*
* This object allows clients to create individual virtual pointer objects.
* @section page_iface_zwlr_virtual_pointer_manager_v1_api API
* See @ref iface_zwlr_virtual_pointer_manager_v1.
*/
/**
* @defgroup iface_zwlr_virtual_pointer_manager_v1 The zwlr_virtual_pointer_manager_v1 interface
*
* This object allows clients to create individual virtual pointer objects.
*/
extern const struct wl_interface zwlr_virtual_pointer_manager_v1_interface;
#endif
#ifndef ZWLR_VIRTUAL_POINTER_V1_ERROR_ENUM
#define ZWLR_VIRTUAL_POINTER_V1_ERROR_ENUM
enum zwlr_virtual_pointer_v1_error {
/**
* client sent invalid axis enumeration value
*/
ZWLR_VIRTUAL_POINTER_V1_ERROR_INVALID_AXIS = 0,
/**
* client sent invalid axis source enumeration value
*/
ZWLR_VIRTUAL_POINTER_V1_ERROR_INVALID_AXIS_SOURCE = 1,
};
#endif /* ZWLR_VIRTUAL_POINTER_V1_ERROR_ENUM */
#define ZWLR_VIRTUAL_POINTER_V1_MOTION 0
#define ZWLR_VIRTUAL_POINTER_V1_MOTION_ABSOLUTE 1
#define ZWLR_VIRTUAL_POINTER_V1_BUTTON 2
#define ZWLR_VIRTUAL_POINTER_V1_AXIS 3
#define ZWLR_VIRTUAL_POINTER_V1_FRAME 4
#define ZWLR_VIRTUAL_POINTER_V1_AXIS_SOURCE 5
#define ZWLR_VIRTUAL_POINTER_V1_AXIS_STOP 6
#define ZWLR_VIRTUAL_POINTER_V1_AXIS_DISCRETE 7
#define ZWLR_VIRTUAL_POINTER_V1_DESTROY 8
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*/
#define ZWLR_VIRTUAL_POINTER_V1_MOTION_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*/
#define ZWLR_VIRTUAL_POINTER_V1_MOTION_ABSOLUTE_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*/
#define ZWLR_VIRTUAL_POINTER_V1_BUTTON_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*/
#define ZWLR_VIRTUAL_POINTER_V1_AXIS_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*/
#define ZWLR_VIRTUAL_POINTER_V1_FRAME_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*/
#define ZWLR_VIRTUAL_POINTER_V1_AXIS_SOURCE_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*/
#define ZWLR_VIRTUAL_POINTER_V1_AXIS_STOP_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*/
#define ZWLR_VIRTUAL_POINTER_V1_AXIS_DISCRETE_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*/
#define ZWLR_VIRTUAL_POINTER_V1_DESTROY_SINCE_VERSION 1
/** @ingroup iface_zwlr_virtual_pointer_v1 */
static inline void
zwlr_virtual_pointer_v1_set_user_data(struct zwlr_virtual_pointer_v1 *zwlr_virtual_pointer_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zwlr_virtual_pointer_v1, user_data);
}
/** @ingroup iface_zwlr_virtual_pointer_v1 */
static inline void *
zwlr_virtual_pointer_v1_get_user_data(struct zwlr_virtual_pointer_v1 *zwlr_virtual_pointer_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) zwlr_virtual_pointer_v1);
}
static inline uint32_t
zwlr_virtual_pointer_v1_get_version(struct zwlr_virtual_pointer_v1 *zwlr_virtual_pointer_v1)
{
return wl_proxy_get_version((struct wl_proxy *) zwlr_virtual_pointer_v1);
}
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*
* The pointer has moved by a relative amount to the previous request.
*
* Values are in the global compositor space.
*/
static inline void
zwlr_virtual_pointer_v1_motion(struct zwlr_virtual_pointer_v1 *zwlr_virtual_pointer_v1, uint32_t time, wl_fixed_t dx, wl_fixed_t dy)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwlr_virtual_pointer_v1,
ZWLR_VIRTUAL_POINTER_V1_MOTION, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_virtual_pointer_v1), 0, time, dx, dy);
}
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*
* The pointer has moved in an absolute coordinate frame.
*
* Value of x can range from 0 to x_extent, value of y can range from 0
* to y_extent.
*/
static inline void
zwlr_virtual_pointer_v1_motion_absolute(struct zwlr_virtual_pointer_v1 *zwlr_virtual_pointer_v1, uint32_t time, uint32_t x, uint32_t y, uint32_t x_extent, uint32_t y_extent)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwlr_virtual_pointer_v1,
ZWLR_VIRTUAL_POINTER_V1_MOTION_ABSOLUTE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_virtual_pointer_v1), 0, time, x, y, x_extent, y_extent);
}
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*
* A button was pressed or released.
*/
static inline void
zwlr_virtual_pointer_v1_button(struct zwlr_virtual_pointer_v1 *zwlr_virtual_pointer_v1, uint32_t time, uint32_t button, uint32_t state)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwlr_virtual_pointer_v1,
ZWLR_VIRTUAL_POINTER_V1_BUTTON, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_virtual_pointer_v1), 0, time, button, state);
}
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*
* Scroll and other axis requests.
*/
static inline void
zwlr_virtual_pointer_v1_axis(struct zwlr_virtual_pointer_v1 *zwlr_virtual_pointer_v1, uint32_t time, uint32_t axis, wl_fixed_t value)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwlr_virtual_pointer_v1,
ZWLR_VIRTUAL_POINTER_V1_AXIS, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_virtual_pointer_v1), 0, time, axis, value);
}
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*
* Indicates the set of events that logically belong together.
*/
static inline void
zwlr_virtual_pointer_v1_frame(struct zwlr_virtual_pointer_v1 *zwlr_virtual_pointer_v1)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwlr_virtual_pointer_v1,
ZWLR_VIRTUAL_POINTER_V1_FRAME, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_virtual_pointer_v1), 0);
}
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*
* Source information for scroll and other axis.
*/
static inline void
zwlr_virtual_pointer_v1_axis_source(struct zwlr_virtual_pointer_v1 *zwlr_virtual_pointer_v1, uint32_t axis_source)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwlr_virtual_pointer_v1,
ZWLR_VIRTUAL_POINTER_V1_AXIS_SOURCE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_virtual_pointer_v1), 0, axis_source);
}
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*
* Stop notification for scroll and other axes.
*/
static inline void
zwlr_virtual_pointer_v1_axis_stop(struct zwlr_virtual_pointer_v1 *zwlr_virtual_pointer_v1, uint32_t time, uint32_t axis)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwlr_virtual_pointer_v1,
ZWLR_VIRTUAL_POINTER_V1_AXIS_STOP, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_virtual_pointer_v1), 0, time, axis);
}
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*
* Discrete step information for scroll and other axes.
*
* This event allows the client to extend data normally sent using the axis
* event with discrete value.
*/
static inline void
zwlr_virtual_pointer_v1_axis_discrete(struct zwlr_virtual_pointer_v1 *zwlr_virtual_pointer_v1, uint32_t time, uint32_t axis, wl_fixed_t value, int32_t discrete)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwlr_virtual_pointer_v1,
ZWLR_VIRTUAL_POINTER_V1_AXIS_DISCRETE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_virtual_pointer_v1), 0, time, axis, value, discrete);
}
/**
* @ingroup iface_zwlr_virtual_pointer_v1
*/
static inline void
zwlr_virtual_pointer_v1_destroy(struct zwlr_virtual_pointer_v1 *zwlr_virtual_pointer_v1)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwlr_virtual_pointer_v1,
ZWLR_VIRTUAL_POINTER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_virtual_pointer_v1), WL_MARSHAL_FLAG_DESTROY);
}
#define ZWLR_VIRTUAL_POINTER_MANAGER_V1_CREATE_VIRTUAL_POINTER 0
#define ZWLR_VIRTUAL_POINTER_MANAGER_V1_DESTROY 1
#define ZWLR_VIRTUAL_POINTER_MANAGER_V1_CREATE_VIRTUAL_POINTER_WITH_OUTPUT 2
/**
* @ingroup iface_zwlr_virtual_pointer_manager_v1
*/
#define ZWLR_VIRTUAL_POINTER_MANAGER_V1_CREATE_VIRTUAL_POINTER_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_virtual_pointer_manager_v1
*/
#define ZWLR_VIRTUAL_POINTER_MANAGER_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zwlr_virtual_pointer_manager_v1
*/
#define ZWLR_VIRTUAL_POINTER_MANAGER_V1_CREATE_VIRTUAL_POINTER_WITH_OUTPUT_SINCE_VERSION 2
/** @ingroup iface_zwlr_virtual_pointer_manager_v1 */
static inline void
zwlr_virtual_pointer_manager_v1_set_user_data(struct zwlr_virtual_pointer_manager_v1 *zwlr_virtual_pointer_manager_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zwlr_virtual_pointer_manager_v1, user_data);
}
/** @ingroup iface_zwlr_virtual_pointer_manager_v1 */
static inline void *
zwlr_virtual_pointer_manager_v1_get_user_data(struct zwlr_virtual_pointer_manager_v1 *zwlr_virtual_pointer_manager_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) zwlr_virtual_pointer_manager_v1);
}
static inline uint32_t
zwlr_virtual_pointer_manager_v1_get_version(struct zwlr_virtual_pointer_manager_v1 *zwlr_virtual_pointer_manager_v1)
{
return wl_proxy_get_version((struct wl_proxy *) zwlr_virtual_pointer_manager_v1);
}
/**
* @ingroup iface_zwlr_virtual_pointer_manager_v1
*
* Creates a new virtual pointer. The optional seat is a suggestion to the
* compositor.
*/
static inline struct zwlr_virtual_pointer_v1 *
zwlr_virtual_pointer_manager_v1_create_virtual_pointer(struct zwlr_virtual_pointer_manager_v1 *zwlr_virtual_pointer_manager_v1, struct wl_seat *seat)
{
struct wl_proxy *id;
id = wl_proxy_marshal_flags((struct wl_proxy *) zwlr_virtual_pointer_manager_v1,
ZWLR_VIRTUAL_POINTER_MANAGER_V1_CREATE_VIRTUAL_POINTER, &zwlr_virtual_pointer_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwlr_virtual_pointer_manager_v1), 0, seat, NULL);
return (struct zwlr_virtual_pointer_v1 *) id;
}
/**
* @ingroup iface_zwlr_virtual_pointer_manager_v1
*/
static inline void
zwlr_virtual_pointer_manager_v1_destroy(struct zwlr_virtual_pointer_manager_v1 *zwlr_virtual_pointer_manager_v1)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwlr_virtual_pointer_manager_v1,
ZWLR_VIRTUAL_POINTER_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_virtual_pointer_manager_v1), WL_MARSHAL_FLAG_DESTROY);
}
/**
* @ingroup iface_zwlr_virtual_pointer_manager_v1
*
* Creates a new virtual pointer. The seat and the output arguments are
* optional. If the seat argument is set, the compositor should assign the
* input device to the requested seat. If the output argument is set, the
* compositor should map the input device to the requested output.
*/
static inline struct zwlr_virtual_pointer_v1 *
zwlr_virtual_pointer_manager_v1_create_virtual_pointer_with_output(struct zwlr_virtual_pointer_manager_v1 *zwlr_virtual_pointer_manager_v1, struct wl_seat *seat, struct wl_output *output)
{
struct wl_proxy *id;
id = wl_proxy_marshal_flags((struct wl_proxy *) zwlr_virtual_pointer_manager_v1,
ZWLR_VIRTUAL_POINTER_MANAGER_V1_CREATE_VIRTUAL_POINTER_WITH_OUTPUT, &zwlr_virtual_pointer_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwlr_virtual_pointer_manager_v1), 0, seat, output, NULL);
return (struct zwlr_virtual_pointer_v1 *) id;
}
#ifdef __cplusplus
}
#endif
#endif
+87
View File
@@ -0,0 +1,87 @@
/* Generated by wayland-scanner 1.23.1 */
/*
* Copyright © 2019 Josef Gajdusek
*
* 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 furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) 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,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_output_interface;
extern const struct wl_interface wl_seat_interface;
extern const struct wl_interface zwlr_virtual_pointer_v1_interface;
static const struct wl_interface *wlr_virtual_pointer_unstable_v1_types[] = {
NULL,
NULL,
NULL,
NULL,
NULL,
&wl_seat_interface,
&zwlr_virtual_pointer_v1_interface,
&wl_seat_interface,
&wl_output_interface,
&zwlr_virtual_pointer_v1_interface,
};
static const struct wl_message zwlr_virtual_pointer_v1_requests[] = {
{ "motion", "uff", wlr_virtual_pointer_unstable_v1_types + 0 },
{ "motion_absolute", "uuuuu", wlr_virtual_pointer_unstable_v1_types + 0 },
{ "button", "uuu", wlr_virtual_pointer_unstable_v1_types + 0 },
{ "axis", "uuf", wlr_virtual_pointer_unstable_v1_types + 0 },
{ "frame", "", wlr_virtual_pointer_unstable_v1_types + 0 },
{ "axis_source", "u", wlr_virtual_pointer_unstable_v1_types + 0 },
{ "axis_stop", "uu", wlr_virtual_pointer_unstable_v1_types + 0 },
{ "axis_discrete", "uufi", wlr_virtual_pointer_unstable_v1_types + 0 },
{ "destroy", "", wlr_virtual_pointer_unstable_v1_types + 0 },
};
WL_PRIVATE const struct wl_interface zwlr_virtual_pointer_v1_interface = {
"zwlr_virtual_pointer_v1", 2,
9, zwlr_virtual_pointer_v1_requests,
0, NULL,
};
static const struct wl_message zwlr_virtual_pointer_manager_v1_requests[] = {
{ "create_virtual_pointer", "?on", wlr_virtual_pointer_unstable_v1_types + 5 },
{ "destroy", "", wlr_virtual_pointer_unstable_v1_types + 0 },
{ "create_virtual_pointer_with_output", "2?o?on", wlr_virtual_pointer_unstable_v1_types + 7 },
};
WL_PRIVATE const struct wl_interface zwlr_virtual_pointer_manager_v1_interface = {
"zwlr_virtual_pointer_manager_v1", 2,
3, zwlr_virtual_pointer_manager_v1_requests,
0, NULL,
};
+109
View File
@@ -0,0 +1,109 @@
// SPDX-FileCopyrightText: 2026 GXDE OS Team
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Headless checks for the pure (non widget) parts of the content factory.
#include <QCoreApplication>
#include <QDebug>
#include <QTextStream>
#include "actions/texttools.h"
#include "factory/contentclassifier.h"
#include "factory/factoryactions.h"
#include "factory/linktools.h"
static int failures = 0;
static void check(bool condition, const QString &what)
{
QTextStream out(stdout);
if (condition) {
out << "PASS " << what << "\n";
} else {
out << "FAIL " << what << "\n";
++failures;
}
}
static bool hasAction(const QVector<FactoryAction> &actions, const QString &id)
{
for (const FactoryAction &action : actions) {
if (action.id == id) {
return true;
}
}
return false;
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// http link with a video extension
const LinkInfo video = LinkTools::parse(QStringLiteral("https://example.com/media/clip.mp4"));
check(video.kind == LinkInfo::Http, "https link recognised");
check(video.extension == QStringLiteral("mp4"), "mp4 extension parsed");
check(video.fileType == QStringLiteral("video"), "mp4 classified as video");
check(hasAction(actionsFor(ContentClassifier::classifyText(QStringLiteral(
"https://example.com/media/clip.mp4"))),
QStringLiteral("download")),
"video link offers download");
check(hasAction(actionsFor(ContentClassifier::classifyText(QStringLiteral(
"https://example.com/media/clip.mp4"))),
QStringLiteral("play")),
"video link offers play");
const LinkInfo image = LinkTools::parse(QStringLiteral("https://example.com/a.png?x=1"));
check(image.fileType == QStringLiteral("image"), "png classified as image");
const LinkInfo page = LinkTools::parse(QStringLiteral("https://example.com/article"));
check(page.fileType == QStringLiteral("page"), "extensionless url is a page");
// thunder:// -> base64("AA" + url + "ZZ")
const QByteArray thunderPayload =
QByteArray("AA") + "http://example.com/file.zip" + QByteArray("ZZ");
const QString thunder =
QStringLiteral("thunder://") + QString::fromLatin1(thunderPayload.toBase64());
const LinkInfo thunderInfo = LinkTools::parse(thunder);
check(thunderInfo.kind == LinkInfo::Custom, "thunder link classified as custom");
check(thunderInfo.decodedUrl == QStringLiteral("http://example.com/file.zip"),
"thunder payload decoded");
check(hasAction(actionsFor(ContentClassifier::classifyText(thunder)),
QStringLiteral("decode")),
"thunder offers decode action");
// magnet
const LinkInfo magnet =
LinkTools::parse(QStringLiteral("magnet:?xt=urn:btih:abc&dn=Some%20File"));
check(magnet.kind == LinkInfo::Custom, "magnet classified as custom");
check(magnet.displayName == QStringLiteral("Some File"), "magnet display name parsed");
// spk://
const LinkInfo spk = LinkTools::parse(QStringLiteral("spk://store/app/com.example"));
check(spk.kind == LinkInfo::Custom, "spk link classified as custom");
// plain text is not a link
const LinkInfo text = LinkTools::parse(QStringLiteral("hello world"));
check(!text.isValid(), "plain text is not a link");
// segmentation
const QStringList words = TextTools::segment(QStringLiteral("我爱北京天安门"));
check(words.size() >= 3, "segmentation produced words");
check(words.contains(QStringLiteral("北京")), "segmentation keeps 北京 together");
// link extraction
const QStringList links = TextTools::extractLinks(
QStringLiteral("看 https://a.example/x?y=1 和 test@example.com,还有 www.b.example"));
check(links.size() == 3, QStringLiteral("extractLinks found 3 links, got %1").arg(links.size()));
// classifier
const DropContent textContent = ContentClassifier::classifyText(QStringLiteral("just words"));
check(textContent.kind == DropContent::Text, "text classified as text");
const DropContent linkContent =
ContentClassifier::classifyText(QStringLiteral("https://example.com/a.zip"));
check(linkContent.kind == DropContent::Link, "link classified as link");
check(hasAction(actionsFor(linkContent), QStringLiteral("save_as")), "link offers save as");
QTextStream(stdout) << (failures == 0 ? "ALL PASS\n" : "FAILURES\n");
return failures == 0 ? 0 : 1;
}