Compare commits

..

1 Commits

Author SHA1 Message Date
Matthias Clasen
f228e13b91 Use pango main as subproject
This is necessary to get the latest pango docs
built in ci.
2023-05-01 22:23:34 -04:00
99 changed files with 745 additions and 1354 deletions

View File

@@ -26,7 +26,7 @@ variables:
BACKEND_FLAGS: "-Dx11-backend=true -Dwayland-backend=true -Dbroadway-backend=true"
FEATURE_FLAGS: "-Dvulkan=enabled -Dcloudproviders=enabled -Ddemos=false -Dbuild-examples=false -Dbuild-tests=false -Dbuild-testsuite=true"
MESON_TEST_TIMEOUT_MULTIPLIER: 3
FEDORA_IMAGE: "registry.gitlab.gnome.org/gnome/gtk/fedora:v46"
FEDORA_IMAGE: "registry.gitlab.gnome.org/gnome/gtk/fedora:v43"
workflow:
rules:
@@ -57,9 +57,15 @@ style-check-diff:
reports:
junit:
- "${CI_PROJECT_DIR}/_build/report-x11.xml"
- "${CI_PROJECT_DIR}/_build/report-x11_unstable.xml"
- "${CI_PROJECT_DIR}/_build/report-wayland.xml"
- "${CI_PROJECT_DIR}/_build/report-wayland_unstable.xml"
- "${CI_PROJECT_DIR}/_build/report-wayland_gles.xml"
- "${CI_PROJECT_DIR}/_build/report-wayland_gles_unstable.xml"
- "${CI_PROJECT_DIR}/_build/report-wayland_smalltexture.xml"
- "${CI_PROJECT_DIR}/_build/report-wayland_smalltexture_unstable.xml"
- "${CI_PROJECT_DIR}/_build/report-broadway.xml"
- "${CI_PROJECT_DIR}/_build/report-broadway_unstable.xml"
name: "gtk-${CI_COMMIT_REF_NAME}"
paths:
- "${CI_PROJECT_DIR}/_build/meson-logs"
@@ -85,6 +91,7 @@ fedora-x86_64:
script:
- .gitlab-ci/show-info-linux.sh
- export PATH="$HOME/.local/bin:$PATH"
- pip3 install --user meson~=0.64
- meson subprojects download
- meson subprojects update --reset
- mkdir _install
@@ -102,6 +109,7 @@ fedora-x86_64:
- .gitlab-ci/run-tests.sh _build x11
- .gitlab-ci/run-tests.sh _build wayland
- .gitlab-ci/run-tests.sh _build wayland_gles
- .gitlab-ci/run-tests.sh _build wayland_smalltexture
- .gitlab-ci/run-tests.sh _build broadway
release-build:
@@ -113,6 +121,7 @@ release-build:
script:
- .gitlab-ci/show-info-linux.sh
- export PATH="$HOME/.local/bin:$PATH"
- pip3 install --user meson~=0.64
- meson subprojects download
- meson subprojects update --reset
- meson setup
@@ -143,11 +152,23 @@ fedora-mingw64:
script:
- .gitlab-ci/show-info-linux.sh
- export PATH="$HOME/.local/bin:$PATH"
- pip3 install --user meson~=1.0
- pip3 install --user meson~=0.64
- meson subprojects download
- meson subprojects update --reset
- meson -Dintrospection=disabled -Dgraphene:introspection=disabled _build
- meson compile -C _build
# Test that mingw64-meson still fails. If it has stopped failing, the CI
# will fail and now you should remove the hack that follows this.
- FAILED=false
- mingw64-meson --version || FAILED=true
- test $FAILED = false && echo "mingw64-meson works now, remove the hack" && exit 1
# HACK: Running mingw64-meson directly fails on the CI with:
# /usr/bin/mingw64-meson: line 92: fg: no job control
# Because rpm is not evaluating %__meson and it gets interpreted as a job
# specifier. So we fix that and run it ourselves.
- rpm --eval "%{mingw64_meson}" > mingw64-meson.sh
- sed -i -e 's/%__meson/meson/' mingw64-meson.sh
- chmod +x mingw64-meson.sh
- ./mingw64-meson.sh -Dintrospection=disabled -Dgraphene:introspection=disabled _build
- ninja -C _build
.mingw-defaults:
stage: build
@@ -189,7 +210,7 @@ macos:
needs: []
before_script:
- bash .gitlab-ci/show-info-osx.sh
- pip3 install --user meson~=1.0
- pip3 install --user meson~=0.64
- pip3 install --user ninja
- export PATH=/Users/gitlabrunner/Library/Python/3.7/bin:$PATH
- export MESON_FORCE_BACKTRACE=1
@@ -347,6 +368,7 @@ static-scan:
EXTRA_MESON_FLAGS: "--buildtype=debug"
script:
- export PATH="$HOME/.local/bin:$PATH"
- pip3 install --user meson~=0.64
- meson setup
${COMMON_MESON_FLAGS}
${EXTRA_MESON_FLAGS}
@@ -368,6 +390,7 @@ asan-build:
variables:
script:
- export PATH="$HOME/.local/bin:$PATH"
- pip3 install --user meson~=0.64
- CC=clang meson setup --buildtype=debugoptimized -Db_sanitize=address -Db_lundef=false -Dintrospection=disabled -Df16c=disabled _build
- ninja -C _build
- .gitlab-ci/run-tests.sh _build wayland
@@ -382,6 +405,7 @@ reference:
needs: []
script:
- export PATH="$HOME/.local/bin:$PATH"
- pip3 install --user meson~=0.64
- meson setup
${COMMON_MESON_FLAGS}
--buildtype=release

View File

@@ -32,6 +32,7 @@ RUN dnf -y install \
glib2-static \
glibc-devel \
glibc-headers \
gnome-desktop-testing \
gnupg2 \
gobject-introspection-devel \
graphene-devel \
@@ -72,14 +73,11 @@ RUN dnf -y install \
mesa-dri-drivers \
mesa-libEGL-devel \
mesa-libGLES-devel \
meson \
mutter \
ninja-build \
pango-devel \
pcre-devel \
pcre-static \
pipewire \
pipewire-gstreamer \
python3 \
python3-docutils \
python3-gobject \
@@ -99,7 +97,6 @@ RUN dnf -y install \
weston \
weston-libs \
which \
wireplumber \
xorg-x11-server-Xvfb \
&& dnf clean all

View File

@@ -138,8 +138,7 @@ if [ $run == 1 ]; then
echo -e "\e[1;32mRUNNING\e[0m: ${base} as ${TAG}"
${CMD} run \
--rm \
--userns=keep-id \
--volume "$(pwd)/..:/home/user/app:rw,z" \
--volume "$(pwd)/..:/home/user/app" \
--workdir "/home/user/app" \
--tty \
--interactive "${TAG}" \

View File

@@ -6,7 +6,6 @@ set +e
srcdir=$( pwd )
builddir=$1
backend=$2
multiplier=${MESON_TEST_TIMEOUT_MULTIPLIER:-1}
# Ignore memory leaks lower in dependencies
export LSAN_OPTIONS=suppressions=$srcdir/lsan.supp:print_suppressions=0:verbosity=1:log_threads=1
@@ -16,8 +15,7 @@ case "${backend}" in
x11)
xvfb-run -a -s "-screen 0 1024x768x24 -noreset" \
meson test -C ${builddir} \
--quiet \
--timeout-multiplier "${multiplier}" \
--timeout-multiplier "${MESON_TEST_TIMEOUT_MULTIPLIER}" \
--print-errorlogs \
--setup=${backend} \
--suite=gtk \
@@ -28,6 +26,14 @@ case "${backend}" in
# Store the exit code for the CI run, but always
# generate the reports
exit_code=$?
xvfb-run -a -s "-screen 0 1024x768x24 -noreset" \
meson test -C ${builddir} \
--timeout-multiplier "${MESON_TEST_TIMEOUT_MULTIPLIER}" \
--print-errorlogs \
--setup=${backend}_unstable \
--suite=flaky \
--suite=failing || true
;;
wayland*)
@@ -38,8 +44,7 @@ case "${backend}" in
export WAYLAND_DISPLAY=wayland-5
meson test -C ${builddir} \
--quiet \
--timeout-multiplier "${multiplier}" \
--timeout-multiplier "${MESON_TEST_TIMEOUT_MULTIPLIER}" \
--print-errorlogs \
--setup=${backend} \
--suite=gtk \
@@ -49,6 +54,13 @@ case "${backend}" in
--no-suite=gsk-compare-broadway
exit_code=$?
meson test -C ${builddir} \
--timeout-multiplier "${MESON_TEST_TIMEOUT_MULTIPLIER}" \
--print-errorlogs \
--setup=${backend}_unstable \
--suite=flaky \
--suite=failing || true
kill ${compositor}
;;
@@ -60,8 +72,7 @@ case "${backend}" in
export BROADWAY_DISPLAY=:5
meson test -C ${builddir} \
--quiet \
--timeout-multiplier "${multiplier}" \
--timeout-multiplier "${MESON_TEST_TIMEOUT_MULTIPLIER}" \
--print-errorlogs \
--setup=${backend} \
--suite=gtk \
@@ -72,6 +83,13 @@ case "${backend}" in
# don't let Broadway failures fail the run, for now
exit_code=0
meson test -C ${builddir} \
--timeout-multiplier "${MESON_TEST_TIMEOUT_MULTIPLIER}" \
--print-errorlogs \
--setup=${backend}_unstable \
--suite=flaky \
--suite=failing || true
kill ${server}
;;
@@ -84,19 +102,20 @@ esac
cd ${builddir}
$srcdir/.gitlab-ci/meson-junit-report.py \
for suffix in "" "_unstable"; do
$srcdir/.gitlab-ci/meson-junit-report.py \
--project-name=gtk \
--backend="${backend}" \
--backend="${backend}${suffix}" \
--job-id="${CI_JOB_NAME}" \
--output="report-${backend}.xml" \
"meson-logs/testlog-${backend}.json"
$srcdir/.gitlab-ci/meson-html-report.py \
--output="report-${backend}${suffix}.xml" \
"meson-logs/testlog-${backend}${suffix}.json"
$srcdir/.gitlab-ci/meson-html-report.py \
--project-name=gtk \
--backend="${backend}" \
--backend="${backend}${suffix}" \
--job-id="${CI_JOB_NAME}" \
--reftest-output-dir="testsuite/reftests/output/${backend}" \
--output="report-${backend}.html" \
"meson-logs/testlog-${backend}.json"
--reftest-output-dir="testsuite/reftests/output/${backend}${suffix}" \
--output="report-${backend}${suffix}.html" \
"meson-logs/testlog-${backend}${suffix}.json"
done
exit $exit_code

View File

@@ -64,7 +64,6 @@ struct _NodeEditorWindow
GListStore *renderers;
GskRenderNode *node;
GFile *file;
GFileMonitor *file_monitor;
GArray *errors;
@@ -545,14 +544,12 @@ node_editor_window_load (NodeEditorWindow *self,
{
GError *error = NULL;
g_clear_object (&self->file);
g_clear_object (&self->file_monitor);
if (!load_file_contents (self, file))
return FALSE;
self->file = g_object_ref (file);
self->file_monitor = g_file_monitor_file (self->file, G_FILE_MONITOR_NONE, NULL, &error);
self->file_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error);
if (error)
{
@@ -589,21 +586,13 @@ static void
show_open_filechooser (NodeEditorWindow *self)
{
GtkFileDialog *dialog;
GFile *cwd;
dialog = gtk_file_dialog_new ();
gtk_file_dialog_set_title (dialog, "Open node file");
if (self->file)
{
gtk_file_dialog_set_initial_file (dialog, self->file);
}
else
{
GFile *cwd;
cwd = g_file_new_for_path (".");
gtk_file_dialog_set_initial_folder (dialog, cwd);
g_object_unref (cwd);
}
cwd = g_file_new_for_path (".");
gtk_file_dialog_set_initial_folder (dialog, cwd);
g_object_unref (cwd);
gtk_file_dialog_open (dialog, GTK_WINDOW (self),
NULL, open_response_cb, self);
g_object_unref (dialog);
@@ -661,21 +650,14 @@ save_cb (GtkWidget *button,
NodeEditorWindow *self)
{
GtkFileDialog *dialog;
GFile *cwd;
dialog = gtk_file_dialog_new ();
gtk_file_dialog_set_title (dialog, "Save node");
if (self->file)
{
gtk_file_dialog_set_initial_file (dialog, self->file);
}
else
{
GFile *cwd = g_file_new_for_path (".");
gtk_file_dialog_set_initial_folder (dialog, cwd);
gtk_file_dialog_set_initial_name (dialog, "demo.node");
g_object_unref (cwd);
}
cwd = g_file_new_for_path (".");
gtk_file_dialog_set_initial_folder (dialog, cwd);
gtk_file_dialog_set_initial_name (dialog, "demo.node");
g_object_unref (cwd);
gtk_file_dialog_save (dialog,
GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (button))),
NULL,
@@ -1109,8 +1091,6 @@ node_editor_window_finalize (GObject *object)
g_clear_pointer (&self->node, gsk_render_node_unref);
g_clear_object (&self->renderers);
g_clear_object (&self->file_monitor);
g_clear_object (&self->file);
G_OBJECT_CLASS (node_editor_window_parent_class)->finalize (object);
}

View File

@@ -78,7 +78,6 @@ gsk_gl_attachment_state_bind_texture (GskGLAttachmentState *self,
target == GL_TEXTURE_2D ||
target == GL_TEXTURE_3D);
g_assert (texture >= GL_TEXTURE0 && texture <= GL_TEXTURE16);
g_assert (texture - GL_TEXTURE0 < G_N_ELEMENTS (self->textures));
attach = &self->textures[texture - GL_TEXTURE0];

View File

@@ -73,13 +73,11 @@ struct _GskGLBindFramebuffer
G_STATIC_ASSERT (sizeof (GskGLBindFramebuffer) == 4);
/* Increase if shaders add more textures */
#define GSK_GL_MAX_TEXTURES_PER_PROGRAM 4
struct _GskGLAttachmentState
{
GskGLBindFramebuffer fbo;
GskGLBindTexture textures[GSK_GL_MAX_TEXTURES_PER_PROGRAM];
/* Increase if shaders add more textures */
GskGLBindTexture textures[4];
guint n_changed;
};

View File

@@ -1028,8 +1028,8 @@ gsk_gl_command_queue_execute (GskGLCommandQueue *self,
G_GNUC_UNUSED unsigned int n_programs = 0;
guint vao_id;
guint vbo_id;
int textures[GSK_GL_MAX_TEXTURES_PER_PROGRAM];
int samplers[GSK_GL_MAX_TEXTURES_PER_PROGRAM];
int textures[4];
int samplers[4];
int framebuffer = -1;
int next_batch_index;
int active = -1;
@@ -1161,8 +1161,6 @@ gsk_gl_command_queue_execute (GskGLCommandQueue *self,
if G_UNLIKELY (batch->draw.bind_count > 0)
{
const GskGLCommandBind *bind = &self->batch_binds.items[batch->draw.bind_offset];
g_assert (bind->texture < G_N_ELEMENTS (textures));
for (guint i = 0; i < batch->draw.bind_count; i++)
{
if (textures[bind->texture] != bind->id)

View File

@@ -1121,21 +1121,19 @@ gsk_gl_driver_lookup_shader (GskGLDriver *self,
}
#ifdef G_ENABLE_DEBUG
void
gsk_gl_driver_save_texture_to_png (GskGLDriver *driver,
int texture_id,
int width,
int height,
const char *filename)
static void
write_atlas_to_png (GskGLDriver *driver,
GskGLTextureAtlas *atlas,
const char *filename)
{
GdkGLTextureBuilder *builder;
GdkTexture *texture;
builder = gdk_gl_texture_builder_new ();
gdk_gl_texture_builder_set_context (builder, gsk_gl_driver_get_context (driver));
gdk_gl_texture_builder_set_id (builder, texture_id);
gdk_gl_texture_builder_set_width (builder, width);
gdk_gl_texture_builder_set_height (builder, height);
gdk_gl_texture_builder_set_id (builder, atlas->texture_id);
gdk_gl_texture_builder_set_width (builder, atlas->width);
gdk_gl_texture_builder_set_height (builder, atlas->height);
texture = gdk_gl_texture_builder_build (builder, NULL, NULL);
gdk_texture_save_to_png (texture, filename);
@@ -1170,7 +1168,7 @@ gsk_gl_driver_save_atlases_to_png (GskGLDriver *self,
G_DIR_SEPARATOR_S,
(int)self->current_frame_id,
atlas->texture_id);
gsk_gl_driver_save_texture_to_png (self, atlas->texture_id, atlas->width, atlas->height, filename);
write_atlas_to_png (self, atlas, filename);
g_free (filename);
}

View File

@@ -176,13 +176,8 @@ GskGLProgram * gsk_gl_driver_lookup_shader (GskGLDriver *s
GError **error);
#ifdef G_ENABLE_DEBUG
void gsk_gl_driver_save_texture_to_png (GskGLDriver *self,
int texture_id,
int width,
int height,
const char *filename);
void gsk_gl_driver_save_atlases_to_png (GskGLDriver *self,
const char *filename);
const char *directory);
#endif
static inline GskGLTexture *

View File

@@ -1750,13 +1750,22 @@ gsk_gl_render_job_visit_rounded_clip_node (GskGLRenderJob *job,
if (job->clip->len <= 1)
need_offscreen = FALSE;
else if (gsk_rounded_rect_contains_rect (&job->current_clip->rect, &transformed_clip.bounds))
else if (rounded_inner_rect_contains_rect (&job->current_clip->rect, &transformed_clip.bounds))
need_offscreen = FALSE;
else
need_offscreen = TRUE;
if (!need_offscreen)
{
/* If the new clip entirely contains the current clip, the intersection is simply
* the current clip, so we can ignore the new one.
*/
if (rounded_inner_rect_contains_rect (&transformed_clip, &job->current_clip->rect.bounds))
{
gsk_gl_render_job_visit_node (job, child);
return;
}
gsk_gl_render_job_push_clip (job, &transformed_clip);
gsk_gl_render_job_visit_node (job, child);
gsk_gl_render_job_pop_clip (job);
@@ -2824,7 +2833,7 @@ gsk_gl_render_job_visit_cross_fade_node (GskGLRenderJob *job,
offscreen_end.reset_clip = TRUE;
offscreen_end.bounds = &node->bounds;
gsk_gl_render_job_set_modelview (job, gsk_transform_scale (NULL, fabs (job->scale_x), fabs (job->scale_y)));
gsk_gl_render_job_set_modelview (job, NULL);
if (!gsk_gl_render_job_visit_node_with_offscreen (job, start_node, &offscreen_start))
{
@@ -3243,7 +3252,7 @@ gsk_gl_render_job_visit_blend_node (GskGLRenderJob *job,
bottom_offscreen.force_offscreen = TRUE;
bottom_offscreen.reset_clip = TRUE;
gsk_gl_render_job_set_modelview (job, gsk_transform_scale (NULL, fabs (job->scale_x), fabs (job->scale_y)));
gsk_gl_render_job_set_modelview (job, NULL);
/* TODO: We create 2 textures here as big as the blend node, but both the
* start and the end node might be a lot smaller than that. */
@@ -3312,7 +3321,7 @@ gsk_gl_render_job_visit_mask_node (GskGLRenderJob *job,
mask_offscreen.reset_clip = TRUE;
mask_offscreen.do_not_cache = TRUE;
gsk_gl_render_job_set_modelview (job, gsk_transform_scale (NULL, fabs (job->scale_x), fabs (job->scale_y)));
gsk_gl_render_job_set_modelview (job, NULL);
/* TODO: We create 2 textures here as big as the mask node, but both
* nodes might be a lot smaller than that.

View File

@@ -871,7 +871,7 @@ create_ascii_glyphs (PangoFont *font)
if (!pango_coverage_get (coverage, i))
break;
}
g_object_unref (coverage);
pango_coverage_unref (coverage);
if (i < MAX_ASCII_GLYPH)
return NULL;

View File

@@ -164,7 +164,7 @@ gskenum_h = gsk_enums[1]
gskresources = gnome.compile_resources('gskresources',
gsk_resources_xml,
dependencies: gsk_private_vulkan_compiled_shaders_deps,
source_dir: [meson.current_build_dir(), meson.current_source_dir()],
source_dir: meson.current_source_dir(),
c_name: '_gsk',
extra_args: [ '--manual-register', ],
)

View File

@@ -1012,8 +1012,9 @@ gsk_vulkan_render_pass_get_node_as_texture (GskVulkanRenderPass *self,
GskVulkanRender *render,
GskVulkanUploader *uploader,
GskRenderNode *node,
const graphene_rect_t *bounds,
GskVulkanClip *current_clip,
graphene_rect_t *tex_bounds)
graphene_rect_t *tex_rect)
{
GskVulkanImage *result;
cairo_surface_t *surface;
@@ -1022,12 +1023,16 @@ gsk_vulkan_render_pass_get_node_as_texture (GskVulkanRenderPass *self,
switch ((guint) gsk_render_node_get_node_type (node))
{
case GSK_TEXTURE_NODE:
result = gsk_vulkan_renderer_ref_texture_image (GSK_VULKAN_RENDERER (gsk_vulkan_render_get_renderer (render)),
gsk_texture_node_get_texture (node),
uploader);
gsk_vulkan_render_add_cleanup_image (render, result);
*tex_bounds = node->bounds;
return result;
if (graphene_rect_equal (bounds, &node->bounds))
{
result = gsk_vulkan_renderer_ref_texture_image (GSK_VULKAN_RENDERER (gsk_vulkan_render_get_renderer (render)),
gsk_texture_node_get_texture (node),
uploader);
gsk_vulkan_render_add_cleanup_image (render, result);
*tex_rect = GRAPHENE_RECT_INIT(0, 0, 1, 1);
return result;
}
break;
case GSK_CAIRO_NODE:
/* We're using recording surfaces, so drawing them to an image
@@ -1045,9 +1050,9 @@ gsk_vulkan_render_pass_get_node_as_texture (GskVulkanRenderPass *self,
graphene_rect_t clipped;
if (current_clip)
graphene_rect_intersection (&current_clip->rect.bounds, &node->bounds, &clipped);
graphene_rect_intersection (&current_clip->rect.bounds, bounds, &clipped);
else
clipped = node->bounds;
clipped = *bounds;
if (clipped.size.width == 0 || clipped.size.height == 0)
return NULL;
@@ -1106,30 +1111,33 @@ gsk_vulkan_render_pass_get_node_as_texture (GskVulkanRenderPass *self,
/* assuming the unclipped bounds should go to texture coordinates 0..1,
* calculate the coordinates for the clipped texture size
*/
*tex_bounds = clipped;
tex_rect->origin.x = (bounds->origin.x - clipped.origin.x)/clipped.size.width;
tex_rect->origin.y = (bounds->origin.y - clipped.origin.y)/clipped.size.height;
tex_rect->size.width = bounds->size.width/clipped.size.width;
tex_rect->size.height = bounds->size.height/clipped.size.height;
return result;
}
}
GSK_RENDERER_DEBUG (gsk_vulkan_render_get_renderer (render), FALLBACK, "Node as texture not implemented for this case. Using %gx%g fallback surface",
ceil (node->bounds.size.width),
ceil (node->bounds.size.height));
ceil (bounds->size.width),
ceil (bounds->size.height));
#ifdef G_ENABLE_DEBUG
{
GskProfiler *profiler = gsk_renderer_get_profiler (gsk_vulkan_render_get_renderer (render));
gsk_profiler_counter_add (profiler,
self->fallback_pixels,
ceil (node->bounds.size.width) * ceil (node->bounds.size.height));
ceil (bounds->size.width) * ceil (bounds->size.height));
}
#endif
/* XXX: We could intersect bounds with clip bounds here */
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
ceil (node->bounds.size.width),
ceil (node->bounds.size.height));
ceil (bounds->size.width),
ceil (bounds->size.height));
cr = cairo_create (surface);
cairo_translate (cr, -node->bounds.origin.x, -node->bounds.origin.y);
cairo_translate (cr, -bounds->origin.x, -bounds->origin.y);
gsk_render_node_draw (node, cr);
@@ -1145,7 +1153,10 @@ gsk_vulkan_render_pass_get_node_as_texture (GskVulkanRenderPass *self,
gsk_vulkan_render_add_cleanup_image (render, result);
*tex_bounds = node->bounds;
tex_rect->origin.x = (node->bounds.origin.x - bounds->origin.x)/bounds->size.width;
tex_rect->origin.y = (node->bounds.origin.y - bounds->origin.y)/bounds->size.height;
tex_rect->size.width = node->bounds.size.width/bounds->size.width;
tex_rect->size.height = node->bounds.size.height/bounds->size.height;
return result;
}
@@ -1239,18 +1250,6 @@ gsk_vulkan_render_pass_upload_fallback (GskVulkanRenderPass *self,
gsk_vulkan_render_add_cleanup_image (render, op->source);
}
static void
get_tex_rect (graphene_rect_t *tex_coords,
const graphene_rect_t *rect,
const graphene_rect_t *tex)
{
graphene_rect_init (tex_coords,
(rect->origin.x - tex->origin.x) / tex->size.width,
(rect->origin.y - tex->origin.y) / tex->size.height,
rect->size.width / tex->size.width,
rect->size.height / tex->size.height);
}
void
gsk_vulkan_render_pass_upload (GskVulkanRenderPass *self,
GskVulkanRender *render,
@@ -1295,61 +1294,63 @@ gsk_vulkan_render_pass_upload (GskVulkanRenderPass *self,
case GSK_VULKAN_OP_OPACITY:
{
GskRenderNode *child = gsk_opacity_node_get_child (op->render.node);
graphene_rect_t tex_bounds;
op->render.source = gsk_vulkan_render_pass_get_node_as_texture (self,
render,
uploader,
child,
&child->bounds,
clip,
&tex_bounds);
get_tex_rect (&op->render.source_rect, &op->render.node->bounds, &tex_bounds);
&op->render.source_rect);
}
break;
case GSK_VULKAN_OP_REPEAT:
{
GskRenderNode *child = gsk_repeat_node_get_child (op->render.node);
const graphene_rect_t *bounds = &op->render.node->bounds;
const graphene_rect_t *child_bounds = gsk_repeat_node_get_child_bounds (op->render.node);
graphene_rect_t tex_bounds;
op->render.source = gsk_vulkan_render_pass_get_node_as_texture (self,
render,
uploader,
child,
child_bounds,
NULL,
&tex_bounds);
get_tex_rect (&op->render.source_rect, child_bounds, &tex_bounds);
&op->render.source_rect);
op->render.source_rect.origin.x = (bounds->origin.x - child_bounds->origin.x)/child_bounds->size.width;
op->render.source_rect.origin.y = (bounds->origin.y - child_bounds->origin.y)/child_bounds->size.height;
op->render.source_rect.size.width = bounds->size.width / child_bounds->size.width;
op->render.source_rect.size.height = bounds->size.height / child_bounds->size.height;
}
break;
case GSK_VULKAN_OP_BLUR:
{
GskRenderNode *child = gsk_blur_node_get_child (op->render.node);
graphene_rect_t tex_bounds;
op->render.source = gsk_vulkan_render_pass_get_node_as_texture (self,
render,
uploader,
child,
&child->bounds,
clip,
&tex_bounds);
get_tex_rect (&op->render.source_rect, &op->render.node->bounds, &tex_bounds);
&op->render.source_rect);
}
break;
case GSK_VULKAN_OP_COLOR_MATRIX:
{
GskRenderNode *child = gsk_color_matrix_node_get_child (op->render.node);
graphene_rect_t tex_bounds;
op->render.source = gsk_vulkan_render_pass_get_node_as_texture (self,
render,
uploader,
child,
&child->bounds,
clip,
&tex_bounds);
get_tex_rect (&op->render.source_rect, &op->render.node->bounds, &tex_bounds);
&op->render.source_rect);
}
break;
@@ -1357,23 +1358,31 @@ gsk_vulkan_render_pass_upload (GskVulkanRenderPass *self,
{
GskRenderNode *start = gsk_cross_fade_node_get_start_child (op->render.node);
GskRenderNode *end = gsk_cross_fade_node_get_end_child (op->render.node);
graphene_rect_t tex_bounds;
const graphene_rect_t *bounds = &op->render.node->bounds;
op->render.source = gsk_vulkan_render_pass_get_node_as_texture (self,
render,
uploader,
start,
&start->bounds,
clip,
&tex_bounds);
get_tex_rect (&op->render.source_rect, &op->render.node->bounds, &tex_bounds);
&op->render.source_rect);
op->render.source_rect.origin.x = (bounds->origin.x - start->bounds.origin.x)/start->bounds.size.width;
op->render.source_rect.origin.y = (bounds->origin.y - start->bounds.origin.y)/start->bounds.size.height;
op->render.source_rect.size.width = bounds->size.width / start->bounds.size.width;
op->render.source_rect.size.height = bounds->size.height / start->bounds.size.height;
op->render.source2 = gsk_vulkan_render_pass_get_node_as_texture (self,
render,
uploader,
end,
&end->bounds,
clip,
&tex_bounds);
get_tex_rect (&op->render.source2_rect, &op->render.node->bounds, &tex_bounds);
&op->render.source2_rect);
op->render.source2_rect.origin.x = (bounds->origin.x - end->bounds.origin.x)/end->bounds.size.width;
op->render.source2_rect.origin.y = (bounds->origin.y - end->bounds.origin.y)/end->bounds.size.height;
op->render.source2_rect.size.width = bounds->size.width / end->bounds.size.width;
op->render.source2_rect.size.height = bounds->size.height / end->bounds.size.height;
}
break;
@@ -1381,23 +1390,31 @@ gsk_vulkan_render_pass_upload (GskVulkanRenderPass *self,
{
GskRenderNode *top = gsk_blend_node_get_top_child (op->render.node);
GskRenderNode *bottom = gsk_blend_node_get_bottom_child (op->render.node);
graphene_rect_t tex_bounds;
const graphene_rect_t *bounds = &op->render.node->bounds;
op->render.source = gsk_vulkan_render_pass_get_node_as_texture (self,
render,
uploader,
top,
&top->bounds,
clip,
&tex_bounds);
get_tex_rect (&op->render.source_rect, &op->render.node->bounds, &tex_bounds);
&op->render.source_rect);
op->render.source_rect.origin.x = (bounds->origin.x - top->bounds.origin.x)/top->bounds.size.width;
op->render.source_rect.origin.y = (bounds->origin.y - top->bounds.origin.y)/top->bounds.size.height;
op->render.source_rect.size.width = bounds->size.width / top->bounds.size.width;
op->render.source_rect.size.height = bounds->size.height / top->bounds.size.height;
op->render.source2 = gsk_vulkan_render_pass_get_node_as_texture (self,
render,
uploader,
bottom,
&bottom->bounds,
clip,
&tex_bounds);
get_tex_rect (&op->render.source2_rect, &op->render.node->bounds, &tex_bounds);
&op->render.source2_rect);
op->render.source2_rect.origin.x = (bounds->origin.x - bottom->bounds.origin.x)/bottom->bounds.size.width;
op->render.source2_rect.origin.y = (bounds->origin.y - bottom->bounds.origin.y)/bottom->bounds.size.height;
op->render.source2_rect.size.width = bounds->size.width / bottom->bounds.size.width;
op->render.source2_rect.size.height = bounds->size.height / bottom->bounds.size.height;
}
break;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -227,7 +227,7 @@ gtk_application_impl_quartz_active_window_changed (GtkApplicationImpl *impl,
* Without this, we might hold on to a reference of the window
* preventing it from getting disposed.
*/
if (window != NULL && !g_object_get_data (G_OBJECT (window), "quartz-muxer-unmap"))
if (window != NULL && !g_object_get_data (G_OBJECT (window), "quartz-muxer-umap"))
{
gulong handler_id = g_signal_connect_object (window,
"unmap",

View File

@@ -102,10 +102,10 @@ gtk_css_filter_clear (GtkCssFilter *filter)
}
static void
gtk_css_filter_init_identity (GtkCssFilter *filter,
const GtkCssFilter *other)
gtk_css_filter_init_identity (GtkCssFilter *filter,
GtkCssFilterType type)
{
switch (other->type)
switch (type)
{
case GTK_CSS_FILTER_BRIGHTNESS:
filter->brightness.value = _gtk_css_number_value_new (1, GTK_CSS_NUMBER);
@@ -135,7 +135,7 @@ gtk_css_filter_init_identity (GtkCssFilter *filter,
filter->blur.value = _gtk_css_number_value_new (0, GTK_CSS_PX);
break;
case GTK_CSS_FILTER_DROP_SHADOW:
filter->drop_shadow.value = gtk_css_shadow_value_new_filter (other->drop_shadow.value);
filter->drop_shadow.value = gtk_css_shadow_value_new_filter ();
break;
case GTK_CSS_FILTER_NONE:
default:
@@ -143,7 +143,7 @@ gtk_css_filter_init_identity (GtkCssFilter *filter,
break;
}
filter->type = other->type;
filter->type = type;
}
#define R 0.2126
@@ -464,7 +464,7 @@ gtk_css_value_filter_equal (const GtkCssValue *value1,
{
GtkCssFilter filter;
gtk_css_filter_init_identity (&filter, &larger->filters[i]);
gtk_css_filter_init_identity (&filter, larger->filters[i].type);
if (!gtk_css_filter_equal (&larger->filters[i], &filter))
{
@@ -588,7 +588,7 @@ gtk_css_value_filter_transition (GtkCssValue *start,
{
GtkCssFilter filter;
gtk_css_filter_init_identity (&filter, &start->filters[i]);
gtk_css_filter_init_identity (&filter, start->filters[i].type);
gtk_css_filter_transition (&result->filters[i],
&start->filters[i],
&filter,
@@ -600,7 +600,7 @@ gtk_css_value_filter_transition (GtkCssValue *start,
{
GtkCssFilter filter;
gtk_css_filter_init_identity (&filter, &end->filters[i]);
gtk_css_filter_init_identity (&filter, end->filters[i].type);
gtk_css_filter_transition (&result->filters[i],
&filter,
&end->filters[i],

View File

@@ -331,7 +331,7 @@ gtk_css_shadow_value_new (ShadowValue *shadows,
}
GtkCssValue *
gtk_css_shadow_value_new_filter (const GtkCssValue *other)
gtk_css_shadow_value_new_filter (void)
{
ShadowValue value;
@@ -340,7 +340,7 @@ gtk_css_shadow_value_new_filter (const GtkCssValue *other)
value.voffset = _gtk_css_number_value_new (0, GTK_CSS_NUMBER);
value.radius = _gtk_css_number_value_new (0, GTK_CSS_NUMBER);
value.spread = _gtk_css_number_value_new (0, GTK_CSS_NUMBER);
value.color = gtk_css_value_ref (other->shadows[0].color);
value.color = _gtk_css_color_value_new_current_color ();
return gtk_css_shadow_value_new (&value, 1, TRUE);
}

View File

@@ -34,7 +34,7 @@
G_BEGIN_DECLS
GtkCssValue * gtk_css_shadow_value_new_none (void);
GtkCssValue * gtk_css_shadow_value_new_filter (const GtkCssValue *other);
GtkCssValue * gtk_css_shadow_value_new_filter (void);
GtkCssValue * gtk_css_shadow_value_parse (GtkCssParser *parser,
gboolean box_shadow_mode);

View File

@@ -637,10 +637,8 @@ gtk_init_check (void)
* applications. It will initialize everything needed to operate the
* toolkit.
*
* If you are using `GtkApplication`, you usually don't have to call this
* function; the `GApplication::startup` handler does it for you. Though,
* if you are using GApplication methods that will be invoked before `startup`,
* such as `local_command_line`, you may need to initialize stuff explicitly.
* If you are using `GtkApplication`, you don't have to call this
* function; the `GApplication::startup` handler does it for you.
*
* This function will terminate your program if it was unable to
* initialize the windowing system for some reason. If you want

View File

@@ -387,8 +387,7 @@ cairo_dep = dependency('cairo', version: cairo_req,
default_options: ['zlib=enabled', 'tests=disabled'])
cairogobj_dep = dependency('cairo-gobject', version: cairo_req)
pango_dep = dependency('pango', version: pango_req)
fribidi_dep = dependency('fribidi', version: fribidi_req,
default_options: ['docs=false'])
fribidi_dep = dependency('fribidi', version: fribidi_req)
harfbuzz_dep = dependency('harfbuzz', version: harfbuzz_req,
default_options: ['coretext=enabled'])

605
po/fa.po

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
directory = fribidi
url = https://github.com/fribidi/fribidi.git
push-url = git@github.com:fribidi/fribidi.git
revision = master
revision = v1.0.12
depth = 1
[provide]

View File

@@ -2,7 +2,6 @@
directory = pango
url = https://gitlab.gnome.org/GNOME/pango.git
push-url = ssh://git@ssh.gitlab.gnome.org:GNOME/pango.git
# needs to be main, because we build the nightly docs from the subproject
revision = main
depth = 1

View File

@@ -37,7 +37,6 @@ test_rectangle_intersect (void)
/* non-empty, non-intersecting rectangles */
res = gdk_rectangle_intersect (&e, &f, &f);
g_assert_false (res);
g_assert_cmpint (f.width, ==, 0);
g_assert_cmpint (f.height, ==, 0);

View File

@@ -114,8 +114,7 @@ compare_xfails = [
compare_xfails_small_texture = [
'big-checkerboard',
'big-checkerboard-scaled-down',
'big-checkerboard-scaled-down2'
'big-checkerboard-scaled-down'
]
foreach renderer : renderers
@@ -136,7 +135,7 @@ foreach renderer : renderers
endif
if compare_xfails_small_texture.contains(testname)
suites += 'wayland_gles_failing'
suites += 'wayland_smalltexture_failing'
endif
if ((exclude_term == '' or not testname.contains(exclude_term)) and

View File

@@ -104,6 +104,9 @@ tests = [
{ 'name': 'revealer-size' },
{ 'name': 'widgetorder' },
{ 'name': 'widget-refcount' },
# This test was disabled for long enough that it no longer compiles
#{ 'name': 'window',
# 'suites': ['failing'] },
]
# Tests that test private apis and therefore are linked against libgtk-4.a

248
testsuite/gtk/window.c Normal file
View File

@@ -0,0 +1,248 @@
#include <gtk/gtk.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/x11/gdkx.h>
#include <X11/Xatom.h>
#endif
static gboolean interactive = FALSE;
static gboolean
stop_main (gpointer data)
{
gboolean *done = data;
*done = TRUE;
g_main_context_wakeup (NULL);
return G_SOURCE_REMOVE;
}
static void
on_draw (GtkDrawingArea *da,
cairo_t *cr,
int width,
int height,
gpointer data)
{
int i, j;
for (i = 0; 20 * i < width; i++)
{
for (j = 0; 20 * j < height; j++)
{
if ((i + j) % 2 == 1)
cairo_set_source_rgb (cr, 1., 1., 1.);
else
cairo_set_source_rgb (cr, 0., 0., 0.);
cairo_rectangle (cr, 20. * i, 20. *j, 20., 20.);
cairo_fill (cr);
}
}
}
static gboolean
on_keypress (GtkEventControllerKey *key,
guint keyval,
guint keycode,
GdkModifierType state,
gpointer data)
{
gboolean *done = data;
*done = TRUE;
g_main_context_wakeup (NULL);
return GDK_EVENT_PROPAGATE;
}
static void
test_default_size (void)
{
GtkWidget *window;
GtkWidget *da;
int w, h;
gboolean done;
window = gtk_window_new ();
if (interactive)
{
GtkEventController *controller = gtk_event_controller_key_new ();
g_signal_connect (controller, "key-pressed", G_CALLBACK (on_keypress), &done);
gtk_widget_add_controller (window, controller);
}
da = gtk_drawing_area_new ();
gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (da), on_draw, NULL, NULL);
gtk_window_set_child (GTK_WINDOW (window), da);
/* check that default size is unset initially */
gtk_window_get_default_size (GTK_WINDOW (window), &w, &h);
g_assert_cmpint (w, ==, -1);
g_assert_cmpint (h, ==, -1);
/* check that setting default size before realize works */
gtk_window_set_default_size (GTK_WINDOW (window), 300, 300);
gtk_window_get_default_size (GTK_WINDOW (window), &w, &h);
g_assert_cmpint (w, ==, 300);
g_assert_cmpint (h, ==, 300);
/* check that the window size is also reported accordingly */
gtk_window_get_size (GTK_WINDOW (window), &w, &h);
g_assert_cmpint (w, ==, 300);
g_assert_cmpint (h, ==, 300);
gtk_window_present (GTK_WINDOW (window));
done = FALSE;
if (!interactive)
g_timeout_add (200, stop_main, &done);
while (!done)
g_main_context_iteration (NULL, TRUE);
/* check that the window and its content actually gets the right size */
gtk_window_get_size (GTK_WINDOW (window), &w, &h);
g_assert_cmpint (w, ==, 300);
g_assert_cmpint (h, ==, 300);
g_assert_cmpint (gtk_widget_get_allocated_width (da), ==, 300);
g_assert_cmpint (gtk_widget_get_allocated_height (da), ==, 300);
/* check that setting default size after the fact does not change
* window size
*/
gtk_window_set_default_size (GTK_WINDOW (window), 100, 600);
gtk_window_get_default_size (GTK_WINDOW (window), &w, &h);
g_assert_cmpint (w, ==, 100);
g_assert_cmpint (h, ==, 600);
done = FALSE;
if (!interactive)
g_timeout_add (200, stop_main, &done);
while (!done)
g_main_context_iteration (NULL, TRUE);
gtk_window_get_size (GTK_WINDOW (window), &w, &h);
g_assert_cmpint (w, ==, 300);
g_assert_cmpint (h, ==, 300);
/* check that even hide/show does not pull in the new default */
gtk_widget_set_visible (window, FALSE);
gtk_widget_set_visible (window, TRUE);
done = FALSE;
if (!interactive)
g_timeout_add (200, stop_main, &done);
while (!done)
g_main_context_iteration (NULL, TRUE);
gtk_window_get_size (GTK_WINDOW (window), &w, &h);
g_assert_cmpint (w, ==, 300);
g_assert_cmpint (h, ==, 300);
gtk_window_destroy (GTK_WINDOW (window));
}
static void
test_resize_popup (void)
{
GtkWidget *window;
int w, h;
gboolean done;
/* testcase for the dnd window */
window = gtk_window_new ();
gtk_window_set_decorated (GTK_WINDOW (window), FALSE);
gtk_window_set_default_size (GTK_WINDOW (window), 1, 1);
gtk_window_get_size (GTK_WINDOW (window), &w, &h);
g_assert_cmpint (w, ==, 1);
g_assert_cmpint (h, ==, 1);
gtk_window_present (GTK_WINDOW (window));
done = FALSE;
if (!interactive)
g_timeout_add (200, stop_main, &done);
while (!done)
g_main_context_iteration (NULL, TRUE);
gtk_window_get_size (GTK_WINDOW (window), &w, &h);
g_assert_cmpint (w, ==, 1);
g_assert_cmpint (h, ==, 1);
gtk_window_destroy (GTK_WINDOW (window));
}
static void
test_show_hide (void)
{
GtkWidget *window;
int w, h, w1, h1;
gboolean done;
/*http://bugzilla.gnome.org/show_bug.cgi?id=696882 */
/* test that hide/show does not affect the size */
window = gtk_window_new ();
gtk_window_present (GTK_WINDOW (window));
done = FALSE;
if (!interactive)
g_timeout_add (200, stop_main, &done);
while (!done)
g_main_context_iteration (NULL, TRUE);
gtk_window_get_size (GTK_WINDOW (window), &w, &h);
gtk_widget_hide (window);
done = FALSE;
if (!interactive)
g_timeout_add (200, stop_main, &done);
while (!done)
g_main_context_iteration (NULL, TRUE);
gtk_window_get_size (GTK_WINDOW (window), &w1, &h1);
g_assert_cmpint (w, ==, w1);
g_assert_cmpint (h, ==, h1);
gtk_window_present (GTK_WINDOW (window));
done = FALSE;
if (!interactive)
g_timeout_add (200, stop_main, &done);
while (!done)
g_main_context_iteration (NULL, TRUE);
gtk_window_get_size (GTK_WINDOW (window), &w1, &h1);
g_assert_cmpint (w, ==, w1);
g_assert_cmpint (h, ==, h1);
gtk_window_destroy (GTK_WINDOW (window));
}
int
main (int argc, char *argv[])
{
int i;
gtk_test_init (&argc, &argv);
for (i = 0; i < argc; i++)
{
if (g_strcmp0 (argv[i], "--interactive") == 0)
interactive = TRUE;
}
g_test_add_func ("/window/default-size", test_default_size);
g_test_add_func ("/window/resize-popup", test_resize_popup);
g_test_add_func ("/window/show-hide", test_show_hide);
return g_test_run ();
}

View File

@@ -1,586 +0,0 @@
import sys
import os
import subprocess
import gi
gi.require_version('Gdk', '4.0')
gi.require_version('Gtk', '4.0')
from gi.repository import GLib, GObject, Gdk, Gtk
from pydbus import SessionBus
verbose = True
remote_desktop = None
screen_cast = None
session = None
stream_path = None
done = False
def terminate():
sys.exit(1)
loop = None
def quit_cb(loop):
loop.quit()
def wait(millis):
global loop
loop = GLib.MainLoop()
GLib.timeout_add(millis, quit_cb, loop)
loop.run()
display = None
window = None
entry = None
expected_change = None
def key_pressed_cb (controller, keyval, keycode, state):
global expected_change
global loop
if verbose:
print(f'got key press: {keyval}, state {state}')
assert expected_change != None, "Unexpected key press"
assert expected_change['type'] == 'press', "Key press event expected"
assert keyval == expected_change['keyval'], "Unexpected keyval in key press event"
assert state == expected_change['state'], "Unexpected state in key press event"
expected_change = None
loop.quit()
def key_released_cb (controller, keyval, keycode, state):
global expected_change
global loop
if verbose:
print(f'got key release: {keyval}, state {state}')
assert expected_change != None, "Unexpected key release"
assert expected_change['type'] == 'release', "Key release event expected"
assert keyval == expected_change['keyval'], "Unexpected keyval in key release event"
assert state == expected_change['state'], "Unexpected state in key release event"
expected_change = None
loop.quit()
def motion_cb (controller, x, y):
global expected_change
global loop
if verbose:
print(f'got motion: {x}, {y}')
if expected_change != None:
assert expected_change['type'] == 'motion', "Motion event expected"
assert x == expected_change['x'], "Unexpected x coord in motion event"
assert y == expected_change['y'], "Unexpected y coord in motion event"
expected_change = None
loop.quit()
def enter_cb (controller, x, y):
global expected_change
global loop
if verbose:
print(f'got enter: {x}, {y}')
assert expected_change != None, "Unexpected enter"
assert expected_change['type'] == 'enter', "Enter event expected"
assert x == expected_change['x'], "Unexpected x coord in enter event"
assert y == expected_change['y'], "Unexpected y coord in enter event"
expected_change = None
loop.quit()
def pressed_cb(controller, n, x, y):
global expected_change
global loop
if verbose:
print(f'got pressed')
assert expected_change != None, "Unexpected event"
assert expected_change['type'] == 'press', "Button press expected"
assert expected_change['button'] == controller.get_current_button(), "Unexpected button pressed"
assert x == expected_change['x'], "Unexpected x coord in motion event"
assert y == expected_change['y'], "Unexpected y coord in motion event"
expected_change = None
loop.quit()
def released_cb(controller, n, x, y):
global expected_change
global loop
if verbose:
print(f'got released')
assert expected_change != None, "Unexpected event"
assert expected_change['type'] == 'release', "Button release expected"
expected_change = None
loop.quit()
def expect_key_press(keyval, state, timeout):
global expected_change
expected_change = {
'type' : 'press',
'keyval' : keyval,
'state' : state
}
wait(timeout)
assert expected_change == None, "Expected event did not happen"
def expect_key_release(keyval, state, timeout):
global expected_change
expected_change = {
'type' : 'release',
'keyval' : keyval,
'state' : state
}
wait(timeout)
assert expected_change == None, "Expected event did not happen"
def expect_motion(x, y, timeout):
global expected_change
expected_change = {
'type' : 'motion',
'x' : x,
'y' : y
}
wait(timeout)
assert expected_change == None, "Expected event did not happen"
def expect_enter(x, y, timeout):
global expected_change
expected_change = {
'type' : 'enter',
'x' : x,
'y' : y
}
wait(timeout)
assert expected_change == None, "Expected event did not happen"
def expect_button_press(button, x, y, timeout):
global expected_change
expected_change = {
'type' : 'press',
'button' : button,
'x' : x,
'y' : y
}
wait(timeout)
assert expected_change == None, "Button press did not arrive"
def expect_button_release(button, x, y, timeout):
global expected_change
expected_change = {
'type' : 'release',
'button' : button,
'x' : x,
'y' : y
}
wait(timeout)
assert expected_change == None, "Button release did not arrive"
def got_active(object, pspec):
global loop
object.disconnect_by_func(got_active)
loop.quit()
def launch_observer():
global display
global window
if verbose:
print('launch observer')
if display == None:
display = Gdk.Display.open(os.getenv('WAYLAND_DISPLAY'))
window = Gtk.Window.new()
controller = Gtk.EventControllerKey.new()
controller.set_propagation_phase(Gtk.PropagationPhase.CAPTURE)
controller.connect('key-pressed', key_pressed_cb)
controller.connect('key-released', key_released_cb)
window.add_controller(controller)
controller = Gtk.EventControllerMotion.new()
controller.set_propagation_phase(Gtk.PropagationPhase.CAPTURE)
controller.connect('enter', enter_cb)
controller.connect('motion', motion_cb)
window.add_controller(controller)
controller = Gtk.GestureClick.new()
controller.set_propagation_phase(Gtk.PropagationPhase.CAPTURE)
controller.connect('pressed', pressed_cb)
controller.connect('released', released_cb)
window.add_controller(controller)
window.connect('notify::is-active', got_active)
window.maximize()
window.present()
wait(500)
assert window.is_active(), "Observer not active"
assert window.get_width() == 1024, "Window not maximized"
assert window.get_height() == 768, "Window not maximized"
# we need to wait out the map animation, or pointer coords will be off
wait(1000)
def launch_entry():
global display
global window
global entry
if verbose:
print('launch entry')
if display == None:
display = Gdk.Display.open(os.getenv('WAYLAND_DISPLAY'))
window = Gtk.Window.new()
entry = Gtk.Entry.new()
window.set_child(entry)
window.connect('notify::is-active', got_active)
window.maximize()
window.present()
wait(500)
assert window.is_active(), "Observer not active"
assert window.get_width() == 1024, "Window not maximized"
assert window.get_height() == 768, "Window not maximized"
# we need to wait out the map animation, or pointer coords will be off
wait(1000)
def stop_observer():
global window
window.destroy()
window = None
def expect_entry_text(text):
assert text == entry.get_text(), "Unexpected entry text: " + entry.get_text()
def key_press(keyval):
if verbose:
print(f'press key {keyval}')
session.NotifyKeyboardKeysym(keyval, True)
def key_release(keyval):
if verbose:
print(f'release key {keyval}')
session.NotifyKeyboardKeysym(keyval, False)
buttons = {
1 : 0x110,
2 : 0x111,
3 : 0x112
}
def button_press(button):
if verbose:
print(f'press button {button}')
session.NotifyPointerButton(buttons[button], True)
def button_release(button):
if verbose:
print(f'release button {button}')
session.NotifyPointerButton(buttons[button], False)
def pointer_move(x, y):
if verbose:
print(f'pointer move {x} {y}')
session.NotifyPointerMotionAbsolute(stream_path, x, y)
def basic_keyboard_tests():
try:
launch_observer()
key_press(Gdk.KEY_a)
expect_key_press(keyval=Gdk.KEY_a, state=0, timeout=100)
key_release(Gdk.KEY_a)
expect_key_release(keyval=Gdk.KEY_a, state=0, timeout=100)
key_press(Gdk.KEY_Control_L)
expect_key_press(keyval=Gdk.KEY_Control_L, state=0, timeout=100)
key_press(Gdk.KEY_x)
expect_key_press(keyval=Gdk.KEY_x, state=Gdk.ModifierType.CONTROL_MASK, timeout=100)
key_release(Gdk.KEY_Control_L)
expect_key_release(keyval=Gdk.KEY_Control_L, state=Gdk.ModifierType.CONTROL_MASK, timeout=100)
key_release(Gdk.KEY_x)
expect_key_release(keyval=Gdk.KEY_x, state=0, timeout=100)
stop_observer()
except AssertionError as e:
print("Error in basic_keyboard_tests: {0}".format(e))
terminate()
def quick_typing_test():
try:
launch_entry()
key_press(Gdk.KEY_T)
key_release(Gdk.KEY_T)
key_press(Gdk.KEY_e)
key_release(Gdk.KEY_e)
key_press(Gdk.KEY_s)
key_release(Gdk.KEY_s)
key_press(Gdk.KEY_t)
key_release(Gdk.KEY_t)
wait(100)
expect_entry_text("Test")
stop_observer()
except AssertionError as e:
print("Error in quick_typing_test: {0}".format(e))
terminate()
def basic_pointer_tests():
try:
pointer_move(-100.0, -100.0)
launch_observer()
# observer window is maximized, so window coords == global coords
pointer_move(500.0, 300.0)
expect_enter(x=500, y=300, timeout=200)
pointer_move(400.0, 200.0)
expect_motion(x=400, y=200, timeout=200)
button_press(1)
expect_button_press(button=1, x=400, y=200, timeout=200)
pointer_move(220.0, 200.0)
expect_motion(x=220, y=200, timeout=200)
button_release(1)
expect_button_release(button=1, x=220, y=200, timeout=200)
stop_observer()
except AssertionError as e:
print("Error in basic_pointer_tests: {0}".format(e))
terminate()
ds_window = None
ds = None
def drag_begin(controller, drag):
global expected_change
global loop
if verbose:
print(f'got drag begin')
assert expected_change != None, "Unexpected drag begin"
assert expected_change['type'] == 'drag', "Drag begin expected"
expected_change = None
loop.quit()
def launch_drag_source(value):
global display
global ds_window
global ds
if verbose:
print('launch drag source')
if display == None:
display = Gdk.Display.open(os.getenv('WAYLAND_DISPLAY'))
ds_window = Gtk.Window.new()
ds_window.set_title('Drag Source')
ds = Gtk.DragSource.new()
ds.set_content(Gdk.ContentProvider.new_for_value(value))
ds_window.add_controller(ds)
ds.connect('drag-begin', drag_begin)
controller = Gtk.GestureClick.new()
controller.set_propagation_phase(Gtk.PropagationPhase.CAPTURE)
controller.connect('pressed', pressed_cb)
controller.connect('released', released_cb)
ds_window.add_controller(controller)
ds_window.connect('notify::is-active', got_active)
ds_window.maximize()
ds_window.present()
wait(500)
assert ds_window.is_active(), "drag source not active"
assert ds_window.get_width() == 1024, "Window not maximized"
assert ds_window.get_height() == 768, "Window not maximized"
# we need to wait out the map animation, or pointer coords will be off
wait(1000)
def stop_drag_source():
global ds_window
ds_window.destroy()
ds_window = None
dt_window = None
def do_drop(controller, value, x, y):
global expected_change
global loop
if verbose:
print(f'got drop {value}')
assert expected_change != None, "Unexpected drop begin"
assert expected_change['type'] == 'drop', "Drop expected"
assert expected_change['value'] == value, "Unexpected value dropped"
expected_change = None
loop.quit()
def launch_drop_target():
global display
global dt_window
if verbose:
print('launch drop target')
if display == None:
display = Gdk.Display.open(os.getenv('WAYLAND_DISPLAY'))
dt_window = Gtk.Window.new()
dt_window.set_title('Drop Target')
controller = Gtk.DropTarget.new(GObject.TYPE_STRING, Gdk.DragAction.COPY)
dt_window.add_controller(controller)
controller.connect('drop', do_drop)
dt_window.connect('notify::is-active', got_active)
dt_window.maximize()
dt_window.present()
wait(500)
assert dt_window.is_active(), "drop target not active"
assert dt_window.get_width() == 1024, "Window not maximized"
assert dt_window.get_height() == 768, "Window not maximized"
# we need to wait out the map animation, or pointer coords will be off
wait(1000)
def stop_drop_target():
global dt_window
dt_window.destroy()
dt_window = None
def expect_drag(timeout):
global expected_change
expected_change = {
'type' : 'drag',
}
wait(timeout)
assert expected_change == None, "DND operation not started"
def expect_drop(value, timeout):
global expected_change
expected_change = {
'type' : 'drop',
'value' : value
}
wait(timeout)
assert expected_change == None, "Drop has not happened"
def dnd_tests():
try:
pointer_move(-100, -100)
launch_drag_source('abc')
wait(100);
pointer_move(100, 100)
wait(100);
button_press(1)
expect_button_press(button=1, x=100, y=100, timeout=300)
# need to wait out the MIN_TIME_TO_DND
wait(150)
pointer_move(120, 150)
expect_drag(timeout=1000)
launch_drop_target()
wait(100);
button_release(1)
expect_drop('abc', timeout=2000)
stop_drop_target()
stop_drag_source()
except AssertionError as e:
print("Error in dnd_tests: {0}".format(e))
terminate()
def session_closed_cb():
print('Session closed')
def mutter_appeared(name):
global remote_desktop
global session
global stream_path
global done
if verbose:
print("mutter appeared on the bus")
remote_desktop = bus.get('org.gnome.Mutter.RemoteDesktop',
'/org/gnome/Mutter/RemoteDesktop')
device_types = remote_desktop.Get('org.gnome.Mutter.RemoteDesktop', 'SupportedDeviceTypes')
assert device_types & 1 == 1, "No keyboard"
assert device_types & 2 == 2, "No pointer"
screen_cast = bus.get('org.gnome.Mutter.ScreenCast',
'/org/gnome/Mutter/ScreenCast')
session_path = remote_desktop.CreateSession()
session = bus.get('org.gnome.Mutter.RemoteDesktop', session_path)
session.onClosed = session_closed_cb
screen_cast_session_path = screen_cast.CreateSession({ 'remote-desktop-session-id' : GLib.Variant('s', session.SessionId)})
screen_cast_session = bus.get('org.gnome.Mutter.ScreenCast', screen_cast_session_path)
stream_path = screen_cast_session.RecordMonitor('Meta-0', {})
session.Start()
# work around lack of initial devices
key_press(Gdk.KEY_Control_L)
key_release(Gdk.KEY_Control_L)
pointer_move(-100, -100)
basic_keyboard_tests()
basic_pointer_tests()
dnd_tests()
quick_typing_test()
session.Stop()
done = True
def mutter_vanished():
global done
if remote_desktop != None:
if verbose:
print("mutter left the bus")
done = True
bus = SessionBus()
bus.watch_name('org.gnome.Mutter.RemoteDesktop', 0, mutter_appeared, mutter_vanished)
try:
while not done:
GLib.MainContext.default().iteration(True)
except KeyboardInterrupt:
print('Interrupted')

View File

@@ -1,193 +0,0 @@
import sys
import os
import subprocess
import gi
gi.require_version('Gdk', '4.0')
from gi.repository import GLib, Gdk
from pydbus import SessionBus
verbose = True
screen_cast = None
monitors = {}
waiting = False
done = False
monitor_model = None
display = None
def terminate():
for key in monitors:
monitor = monitors[key];
pipeline = monitor['pipeline'];
pipeline.terminate()
sys.exit(1)
def stream_added_closure(name):
def stream_added(node_id):
monitor = monitors[name];
freq = monitor['freq'];
width = monitor['width'];
height = monitor['height'];
# FIXME scale = monitor['scale'];
# Use gstreamer out-of-process, since the gst gl support gets
# itself into a twist with its wayland connection when monitors
# disappear
pipeline_desc = f'gst-launch-1.0 --verbose pipewiresrc path={node_id} ! video/x-raw,max-framerate={freq}/1,width={width},height={height} ! videoconvert ! glimagesink'
if verbose:
print(f'launching {pipeline_desc}')
monitor['pipeline'] = subprocess.Popen([pipeline_desc], shell=True)
return stream_added
def add_monitor(name, width, height, scale, freq):
if verbose:
print(f'add monitor {name}: {width}x{height}, scale {scale}, frequency {freq}')
session_path = screen_cast.CreateSession({})
session = bus.get('org.gnome.Mutter.ScreenCast', session_path)
monitors[name] = {
"session": session,
"width": width,
"height": height,
"scale": scale,
"freq": freq
}
stream_path = session.RecordVirtual({})
stream = bus.get('org.gnome.Mutter.ScreenCast', stream_path)
stream.onPipeWireStreamAdded = stream_added_closure(name)
session.Start()
def remove_monitor(name):
if verbose:
print(f'remove monitor {name}')
try:
monitor = monitors[name];
pipeline = monitor['pipeline']
pipeline.kill()
session = monitor['session']
session.Stop()
except KeyError:
print("failed to remove monitor")
monitors[name] = None
expected_change = None
loop = None
def quit_cb(loop):
loop.quit()
def wait(millis):
global loop
loop = GLib.MainLoop()
GLib.timeout_add(millis, quit_cb, loop)
loop.run()
def monitors_changed(monitors, position, removed, added):
global expected_change
assert expected_change != None, "No change expected"
assert position == expected_change['position'], "Unexpected position in monitors-changed"
assert removed == expected_change['removed'], "Unexpected removed in monitors-changed"
assert added == expected_change['added'], "Unexpected added in monitors-changed"
if verbose:
print('got expected change')
expected_change = None
loop.quit()
def launch_observer():
global monitor_model
global display
if display == None:
display = Gdk.Display.open(os.getenv('WAYLAND_DISPLAY'))
if verbose:
print('launch observer')
monitor_model = display.get_monitors()
assert monitor_model.get_n_items() == 0, "Unexpected initial monitors"
monitor_model.connect('items-changed', monitors_changed)
def expect_monitors_changed(position, removed, added, timeout):
global expected_change
expected_change = {
'position' : position,
'removed' : removed,
'added' : added
}
wait(timeout)
assert expected_change == None, "Expected change did not happen"
def got_connector(monitor, pspec):
loop.quit()
def expect_monitor(position, width, height, scale, freq):
assert monitor_model.get_n_items() > position, f'Monitor {position} not present'
monitor = monitor_model.get_item(position)
if monitor.get_connector() == None:
handler = monitor.connect('notify::connector', got_connector)
wait(500)
monitor.disconnect(handler)
assert monitor.is_valid(), "Monitor is not valid"
geometry = monitor.get_geometry()
assert geometry.width == width, "Unexpected monitor width"
assert geometry.height == height, "Unexpected monitor height"
assert monitor.get_scale_factor() == scale, "Unexpected scale factor"
assert monitor.get_refresh_rate() == freq, "Unexpected monitor frequency"
if verbose:
print(f'monitor {position}: {geometry.width}x{geometry.height} frequency {monitor.get_refresh_rate()} scale {monitor.get_scale_factor()} model \'{monitor.get_model()}\' connector \'{monitor.get_connector()}\'')
def run_commands():
try:
launch_observer()
add_monitor("0", width=100, height=100, scale=1, freq=60)
expect_monitors_changed(0, 0, 1, 5000)
expect_monitor (position=0, width=100, height=100, scale=1, freq=60000)
add_monitor("1", width=1024, height=768, scale=1, freq=144)
expect_monitors_changed(1, 0, 1, 5000)
expect_monitor (position=1, width=1024, height=768, scale=1, freq=144000)
remove_monitor("0")
expect_monitors_changed(0, 1, 0, 11000) # mutter takes 10 seconds to remove it
remove_monitor("1")
expect_monitors_changed(0, 1, 0, 11000)
except AssertionError as e:
print("Error: {0}".format(e))
terminate()
def mutter_appeared(name):
global screen_cast
global done
if verbose:
print("mutter appeared on the bus")
screen_cast = bus.get('org.gnome.Mutter.ScreenCast',
'/org/gnome/Mutter/ScreenCast')
run_commands()
if verbose:
print ("Done running commands, exiting...")
done = True
def mutter_vanished():
global done
if screen_cast != None:
if verbose:
print("mutter left the bus")
done = True
bus = SessionBus()
bus.watch_name('org.gnome.Mutter.ScreenCast', 0, mutter_appeared, mutter_vanished)
try:
while not done:
GLib.MainContext.default().iteration(True)
except KeyboardInterrupt:
print('Interrupted')

View File

@@ -1,19 +0,0 @@
env = environment()
env.prepend('GI_TYPELIB_PATH',
project_build_root / 'gtk',
gi_dep.get_variable(pkgconfig: 'typelibdir'),
)
env.prepend('LD_PRELOAD', project_build_root / 'gtk' / 'libgtk-4.so')
env.prepend('MESON_CURRENT_SOURCE_DIR', meson.current_source_dir())
test('monitor',
find_program('run-headless-monitor-tests.sh', dirs: meson.current_source_dir()),
suite: ['headless'],
env: env,
)
test('input',
find_program('run-headless-input-tests.sh', dirs: meson.current_source_dir()),
suite: ['headless'],
env: env,
)

View File

@@ -1,36 +0,0 @@
#! /bin/sh
srcdir=${MESON_CURRENT_SOURCE_DIR:-./testsuite/headless}
dbus-run-session sh <<EOF
export XDG_RUNTIME_DIR="$(mktemp -p $(pwd) -d xdg-runtime-XXXXXX)"
pipewire &
pipewire_pid=\$!
wireplumber &
wireplumber_pid=\$!
sleep 1
#echo DBUS_SESSION_BUS_ADDRESS=\$DBUS_SESSION_BUS_ADDRESS
#echo WAYLAND_DISPLAY=gtk-test
export GTK_A11Y=none
export GIO_USE_VFS=local
mutter --headless --virtual-monitor 1024x768 --no-x11 --wayland-display gtk-test2 >&mutter2.log &
mutter_pid=\$!
export WAYLAND_DISPLAY=gtk-test2
export GDK_BACKEND=wayland
python3 ${srcdir}/headless-input-tests.py
status=\$?
kill \$mutter_pid
kill \$wireplumber_pid
kill \$pipewire_pid
exit \$status
EOF

View File

@@ -1,42 +0,0 @@
#! /bin/sh
srcdir=${MESON_CURRENT_SOURCE_DIR:-./testsuite/headless}
export GTK_A11Y=none
export GIO_USE_VFS=local
dbus-run-session sh <<EOF
export XDG_RUNTIME_DIR="$(mktemp -p $(pwd) -d xdg-runtime-XXXXXX)"
pipewire &
pipewire_pid=\$!
sleep 2
wireplumber &
wireplumber_pid=\$!
sleep 2
# echo DBUS_SESSION_BUS_ADDRESS=\$DBUS_SESSION_BUS_ADDRESS
# echo WAYLAND_DISPLAY=gtk-test
export MUTTER_DEBUG=screen-cast
mutter --headless --no-x11 --wayland-display gtk-test &
mutter_pid=\$!
sleep 2
export WAYLAND_DISPLAY=gtk-test
export GDK_BACKEND=wayland
python3 ${srcdir}/headless-monitor-tests.py
status=\$?
kill \$mutter_pid
kill \$wireplumber_pid
kill \$pipewire_pid
exit \$status
EOF

View File

@@ -20,10 +20,12 @@ setups = [
'env': ['GDK_DEBUG=gl-gles,default-settings',
'MESA_GLES_VERSION_OVERRIDE=2.0',
'MESA_EXTENSION_OVERRIDE=-GL_OES_vertex_array_object',
'GSK_MAX_TEXTURE_SIZE=1024',
], },
{ 'backend': 'win32', 'if': os_win32 },
{ 'backend': 'broadway', 'if': broadway_enabled, },
{ 'name': 'wayland_smalltexture',
'backend': 'wayland', 'if': wayland_enabled,
'env': ['GSK_MAX_TEXTURE_SIZE=1024'] },
{ 'backend': 'win32', 'if': os_win32 },
]
@@ -37,8 +39,8 @@ foreach setup : setups
exclude += 'gsk-compare-broadway'
endif
if name == 'wayland_gles'
exclude += 'wayland_gles_failing'
if name == 'wayland_smalltexture'
exclude += 'wayland_smalltexture_failing'
endif
env = common_env + [
@@ -68,9 +70,7 @@ subdir('css')
subdir('a11y')
subdir('tools')
subdir('reftests')
if build_gir
subdir('introspection')
endif
if wayland_enabled
subdir('headless')
endif