Skip to content

feat: Linux platform (GTK 4) - #11620

Draft
triniwiz wants to merge 32 commits into
feat/windowsfrom
feat/linux
Draft

triniwiz wants to merge 32 commits into
feat/windowsfrom
feat/linux

Conversation

@triniwiz

@triniwiz triniwiz commented Nov 7, 2026 •

Copy link
Copy Markdown
Member

Adds support for the new Linux runtime (GTK 4).

Part of #27

Adds a linux platform to @nativescript/core and @nativescript/webpack,
running on the NativeScript Linux runtime (C APIs from metadata).

- webpack: linux platform, __LINUX__ / global.isLinux, CommonJS output
- core: isLinux, LinuxApplication, LinuxNativeWindow (GtkWindow)
- views: core's own measure/layout pass (as on iOS); containers are
  GtkFixed with a JS custom layout manager placing children at the
  computed frames, leaf widgets measured with gtk_widget_measure
- styling through per-widget GTK CSS; Pango markup for formatted text
- Label, Button, Page, Frame, ActionBar, layouts, file system (GIO),
  http (runtime fetch), image source (GdkTexture), settings, device,
  screen, connectivity; remaining widgets are placeholders for now
- types-minimal: generated GTK / GLib / Pango / Cairo typings
The program name is the app id from nativescript.config, so desktops match
windows to <app-id>.desktop; the window icon comes from the staged icon
theme. linux.window sets the initial size, minimum size, resizability and
title. res:// images resolve from App_Resources/Linux/images.
…s, ActivityIndicator, SearchBar, HtmlView

- containers inside GTK scrolling viewports report their NativeScript frame
  as their size; only views NativeScript never laid out run a layout pass
- widget CSS changes re-measure the owning view; sub-part styling (switch
  thumb, scale highlight, progress fill, links) via scoped display CSS
Image is a GtkPicture over the source's GdkTexture (measured like iOS),
pickers use GtkCalendar, spin buttons and GtkDropDown, SegmentedBar is a
linked toggle button group. Also fixes TextView sizing (GTK validates text
lines lazily) and drops the platformCheck import that webpack 5.112 leaves
unresolved.
tap / doubleTap (GtkGestureClick), longPress, pan and touch (GtkGestureDrag),
swipe, pinch (GtkGestureZoom) and rotation, with iOS semantics: taps don't
fire for drags or long presses, pans start after movement, still releases
aren't swipes. Signal trampolines now take typed (double / int) arguments.
Modal windows over the app window built from GTK widgets (GTK 4.6 has no
GtkAlertDialog): primary button is the default, Return in an entry submits,
closing the window resolves as cancel unless cancelable is false.
Tweens opacity, backgroundColor, translate, scale, rotate, width and height
with CSS / Core Animation cubic-bezier curves, delays, iterations (including
infinite), sequential playback and cancel. GTK 4.6's GL renderer shears
views that are both rotated and translucent (fine with GSK_RENDERER=cairo
and newer GTK).
Virtualized rows: a GtkStringList of placeholders backs a GtkListView, and
each bound row hosts a NativeScript view from the item template (itemLoading,
template selector, rowHeight, itemTap, loadMoreItems, scrollToIndex).

Views a parent hosts in a GTK widget (list rows, ActionBar.titleView) now
answer GTK's measure with a NativeScript measure and lay themselves out.
Each item's view is a hosted page laid out by NativeScript; items load once
the TabView loads (as on iOS); tab switches feed selectedIndex; tab text,
selected text, background and font size through scoped CSS.
SplitView lays out primary | supplementary | secondary | inspector columns
from the preferred width fractions, with show/hide per role. WebView opens
pages in the desktop browser until WebKitGTK 6.0 is in the metadata.
As on iOS, livesync goes to the root view, which replaces the page whose
module changed and resets the root content when nothing handled it
(Frame.reloadPage is Android only).
@nx-cloud

nx-cloud Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 6122649

Command Status Duration Result
nx run-many -t build -p core,webpack5,vite ✅ Succeeded 25s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-11-12 00:55:57 UTC

@pkg-pr-new

pkg-pr-new Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@nativescript/core@11620
npm i https://pkg.pr.new/@nativescript/vite@11620
npm i https://pkg.pr.new/@nativescript/webpack@11620

commit: 6122649

'system' (the default) follows the desktop. 'light' or 'dark' set GtkSettings for the app, so a light app theme stays readable on a dark desktop, like a fixed Android app theme.
Frames come from the window's GDK frame clock (as CADisplayLink / Choreographer on iOS / Android), in g_get_monotonic_time() milliseconds; a ~60 Hz timer only until the first window exists.
GTK gave the returning page's first focusable widget focus, and a ScrollView holding it scrolled
back to it. The page's focused view is now restored instead.
File, Folder and ImageSource read the app folder through the runtime's
app.nsbundle when one is mounted (zero-copy for unencrypted bundles);
writes inside it fail with EROFS.
Unclosed or stray tags and raw & / < in hrefs made GTK reject the whole
markup (the label kept its old text). Open tags are now tracked and closed,
and href values escaped.
Encodes the PNG in memory instead of leaving ns-image-<ms>.png files (which
collided within the same millisecond) in the cache folder.
HTML content may be untrusted: file://, custom schemes and desktop
launchers no longer open.
…ityIndicator spin (#11623)

* fix(linux): CSS keyframe animations write keyframe values

KeyframeAnimation sets the style's keyframe values when it starts, and they win
over local values, so the Linux Animation's local writes didn't show until the
animation ended (animate-pulse never pulsed). Like iOS and Android, an animation
with valueSource 'keyframe' now writes the keyframe values.

* feat(linux): mouseEnter, mouseLeave and mouseMove events

A GtkEventControllerMotion, attached while any of them has a listener, raises
the events (mouseMove with getX/getY) and keeps the hover visual state, as
Windows does. MasonKit's :hover and onMouseLeave relied on them.

* fix(linux): ActivityIndicator spins when busy is set before it's shown

GTK left the spinner's CSS animation still when gtk_spinner_start ran before
the widget was on screen (or inside its map signal), until a theme change
restyled it. It now starts on the turn after the spinner is mapped.
…er the placeholder

On Linux, Application.run() fires the one-shot launch event while the HMR
placeholder boots, before the app entry loads. Angular registers its launch
listener later and calls run() without an entry, which the patched run()
ignored, so the app never bootstrapped. Replay the launch event once and
install the root a listener supplies.
Core reads the app config with require('~/package.json'), but the core
bundle served it as an ESM default export, so require() returned
{ default } and Linux never set the program name, app name or window
title. Serve it as CommonJS that loads the CLI-written package.json on
device, with nativescript.config as the fallback.
FPSCallback started and stopped continuous updating on the window's frame
clock (through a tick callback) for every animation. GTK 4.6 resets its
frame-time smoothing each time updating starts, and doing that repeatedly
stalls GTK's own CSS animations: a GtkSpinner stops turning while short
view animations run. Request the next UPDATE phase from the clock on each
frame instead, as requestAnimationFrame does.
Forward navigation unloads the outgoing page but leaves it in the frame,
so going back showed it without raising loaded and its views stayed
unloaded.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant