diff --git a/content/best-practices/platform-file-split-or-not.md b/content/best-practices/platform-file-split-or-not.md index 658d0ea4..02d0f732 100644 --- a/content/best-practices/platform-file-split-or-not.md +++ b/content/best-practices/platform-file-split-or-not.md @@ -23,12 +23,13 @@ Using platform files: - `file.ios.ts` - `file.android.ts` +- `file.linux.ts` (when targeting [Linux](/guide/linux/)) The advent of tree shaking and webpack builds does away with quite a bit of worry in this area however there's a few things to consider here. ## Conditional with tree shaking -When speaking of tree shaking ever since NativeScript 7, you've been able to use `__ANDROID__` or `global.isIOS` and anytime those are used as conditional splits in your code, only the applicable code for the platform that's being built would actually end up in your compiled code alleviating a lot of concern here. +When speaking of tree shaking ever since NativeScript 7, you've been able to use `__ANDROID__`, `__LINUX__` or `global.isIOS` and anytime those are used as conditional splits in your code, only the applicable code for the platform that's being built would actually end up in your compiled code alleviating a lot of concern here. ## Future maintenance diff --git a/content/configuration/nativescript.md b/content/configuration/nativescript.md index d874585c..ae5bef7d 100644 --- a/content/configuration/nativescript.md +++ b/content/configuration/nativescript.md @@ -37,7 +37,7 @@ export default { id: string = 'com.mycompany.myapp' ``` -Controls the Application ID of your app, this setting can be overridden per platform via [ios.id](#ios-id) and [android.id](#android-id). +Controls the Application ID of your app, this setting can be overridden per platform via [ios.id](#ios-id), [android.id](#android-id) and [linux.id](#linux-id). ### main @@ -163,6 +163,14 @@ ios: Object = {} See [iOS Configuration Reference](#ios-configuration-reference) +### linux + +```ts +linux: Object = {} +``` + +See [Linux Configuration Reference](#linux-configuration-reference) + ### hooks ```ts @@ -508,6 +516,109 @@ ios: { } ``` +## Linux Configuration Reference + +::: warning Experimental +The Linux platform is experimental. See [Developing for Linux](/guide/linux/). +::: + +### linux.id + +```ts +linux.id: string = 'org.mycompany.MyApp'; +``` + +Controls the app id on Linux, this setting overrides the value set in [id](#id). It names the app's desktop entry (`.desktop`), icon and install folder, and must be a reverse DNS name (letters, digits, `_` and `-`, separated by dots). + +### linux.window + +```ts +linux.window: { + width?: number + height?: number + minWidth?: number + minHeight?: number + resizable?: boolean + title?: string +} = {} +``` + +The initial size of the app's windows, in logical pixels, and their minimum size. Defaults to `1024` by `768`, resizable, with the app name as the title. + +```ts +export default { + // ... + linux: { + window: { + width: 1280, + height: 800, + minWidth: 480, + minHeight: 360, + }, + }, +} as NativeScriptConfig +``` + +### linux.appearance + +```ts +linux.appearance: 'system' | 'light' | 'dark' = 'system'; +``` + +Keeps the app's native controls light or dark, whatever the desktop uses. Defaults to `'system'`, which follows the desktop. + +### linux.v8Flags + +```ts +linux.v8Flags: string = '--expose_gc'; +``` + +V8 flags set before the runtime starts. During development, the `NS_V8_FLAGS` environment variable adds to them. + +### linux.bundle + +```ts +linux.bundle: boolean = true; +``` + +When enabled, **release** builds pack the app's files into one read-only `app.nsbundle` next to the executable. App code reads it with the usual file APIs, and writing to app files fails. Debug builds always use a plain folder. Defaults to `true`. + +### linux.bundleUnpack + +```ts +linux.bundleUnpack: string[] = ['fonts/**']; +``` + +Globs, relative to the app folder, of files that are kept as real files in `app.nsbundle.unpacked`, for code that opens them by path. Native addons (`.node` and `.so` files) are always unpacked. Defaults to `['fonts/**']`. + +### linux.sourceProtect + +```ts +linux.sourceProtect: boolean = true; +``` + +When enabled, **release** builds encrypt the `app.nsbundle` with a key generated for each build and embedded in the executable, so the app's sources aren't shipped readable. Defaults to `false`. + +This can be overridden per build with `--source-protect` or `--no-source-protect`. The encryption key can be provided with `--source-protect-key-hex ` or the `NS_LINUX_BUNDLE_KEY` environment variable. If source protection is requested and can't be applied, the build fails. + +### linux.fuses + +```ts +linux.fuses: { + bundleIntegrity?: boolean + onlyLoadAppFromBundle?: boolean + inspector?: boolean + envOverrides?: boolean +} = {} +``` + +Settings written into the executable of **release** builds: + +- `bundleIntegrity` - refuse an `app.nsbundle` that doesn't match the build. Defaults to `true` when the app is bundled. +- `onlyLoadAppFromBundle` - only run the app from `app.nsbundle`, never from a plain app folder. Defaults to `true` when the app is bundled. +- `inspector` - allow attaching a debugger with `--inspect`. Defaults to `false`. +- `envOverrides` - honor the runtime's command line flags and environment variables (such as `NS_V8_FLAGS`). Defaults to `false`. + ## Hooks Configuration Reference ```ts diff --git a/content/configuration/vite.md b/content/configuration/vite.md index ff666005..92b56488 100644 --- a/content/configuration/vite.md +++ b/content/configuration/vite.md @@ -381,7 +381,8 @@ Additional env flags that are passed by the CLI automatically - `--env.android` - `true` when running on Android - `--env.ios` - `true` when running on iOS - `--env.visionos` - `true` when running on visionOS -- `--env.platform=` - for specifying the platform to use. Can be `android`, `ios`, or `visionos`. +- `--env.linux` - `true` when running on Linux +- `--env.platform=` - for specifying the platform to use. Can be `android`, `ios`, `visionos`, or `linux`. - `--env.hmr` - `true` when building with HMR enabled ## Global "magic" variables @@ -418,6 +419,12 @@ We define a few useful globally available variables that you can use to alter lo // we are running on an Apple platform } ``` +- `__LINUX__`, `true` when the platform is Linux + ```ts + if (__LINUX__) { + // we are running on Linux + } + ``` ::: details The following variables are also defined, but are primarily intended to be used by NativeScript Core internally, or plugins that wish to use these. diff --git a/content/configuration/webpack.md b/content/configuration/webpack.md index d9e79aa1..93796197 100644 --- a/content/configuration/webpack.md +++ b/content/configuration/webpack.md @@ -96,7 +96,8 @@ Additional env flags that are usually passed by the CLI automatically - `--env.nativescriptLibPath` - path to the currently running CLI's library. - `--env.android` - `true` when running on android - `--env.ios` - `true` when running on ios -- `--env.platform=` - for specifying the platform to use. Can be `android` or `ios`, or a custom platform in the future. +- `--env.linux` - `true` when running on Linux +- `--env.platform=` - for specifying the platform to use. Can be `android`, `ios`, `linux`, or a custom platform. - `--env.hmr` - `true` when building with HMR enabled ## Global "magic" variables @@ -121,6 +122,12 @@ We define a few useful globally available variables that you can use to alter lo // we are running on iOS } ``` +- `__LINUX__` (also available as `global.isLinux`) - `true` when the platform is Linux + ```ts + if (__LINUX__) { + // we are running on Linux + } + ``` ::: details The following variables are also defined, but are primarily intended to be used by NativeScript Core internally, or plugins that wish to use these. diff --git a/content/guide/adding-native-code.md b/content/guide/adding-native-code.md index 3b574e0c..f4d58460 100644 --- a/content/guide/adding-native-code.md +++ b/content/guide/adding-native-code.md @@ -30,6 +30,7 @@ ns native add java com.company.OtherAwesomeClass 1. You can also manually add native code to [App_Resources](/project-structure/app-resources): - [Adding Java/Kotlin code to an application](/guide/native-code/android) - [Adding ObjectiveC/Swift Code to an application](/guide/native-code/ios) + - [Adding C/C++ code to a Linux application](/guide/native-code/linux) 2. Optionally [generate TypeScript types for the added APIs](/guide/native-code/generate-typings) Additionally, NativeScript also supports Jetpack Compose and SwiftUI through plugins. diff --git a/content/guide/cli-basics.md b/content/guide/cli-basics.md index a271bba2..6c933cbb 100644 --- a/content/guide/cli-basics.md +++ b/content/guide/cli-basics.md @@ -57,6 +57,8 @@ Example output: | 3 | iPhone 14 Pro | iOS | XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX | Emulator | Connected | Local | ``` +On a Linux host, the local machine is also listed as a `Linux` device. `ns run linux` runs the app on it (see [Developing for Linux](/guide/linux/)). + ## Setting the default package manager To set the default package manager that the CLI uses (unless overridden in [nativescript.config.ts](/project-structure/nativescript-config#cli-packagemanager)): diff --git a/content/guide/debugging.md b/content/guide/debugging.md index 666825f4..acf9f58b 100644 --- a/content/guide/debugging.md +++ b/content/guide/debugging.md @@ -5,7 +5,7 @@ contributors: - rigor789 --- -There are multiple ways to debug issues in your apps, starting with the simplest form using `console.logs`. For more complex issues, you may need to use an actual debugger, like Chrome DevTools, XCode developer tools and instruments or the Android Studio developer tools. +There are multiple ways to debug issues in your apps, starting with the simplest form using `console.logs`. For more complex issues, you may need to use an actual debugger, like Chrome DevTools, XCode developer tools and instruments, the Android Studio developer tools or GDB on Linux. ## Console @@ -41,7 +41,7 @@ console.timeEnd('myLabel') To start a Chrome debugging session, run your app in debug mode: ```bash -ns debug android|ios +ns debug android|ios|linux ``` The `ns debug` command builds and deploys the app on a connected device or emulator, in case you have multiple devices available you will need to pick one from a list, or pass in the `--device ` from `ns devices`. @@ -163,3 +163,58 @@ Since NativeScript follows a standard gradle/android application structure, you - [Android Studio: Layout Inspector](https://developer.android.com/studio/debug/layout-inspector) - [Android Studio: view Logcat logs](https://developer.android.com/studio/debug/am-logcat) - [Androud Studio: Debug your app](https://developer.android.com/studio/debug#startdebug) + +## Debugging on Linux + +::: warning Experimental +The Linux platform is experimental. See [Developing for Linux](/guide/linux/). +::: + +### Chrome DevTools + +Start a debug session on the local machine with: + +```bash +ns debug linux +``` + +The command builds and launches the app with the V8 inspector enabled. Once the inspector is listening, a URL is printed to the console: + +```bash +To start debugging, open the following URL in Chrome: +devtools://devtools/bundled/inspector.html?ws=127.0.0.1:9229/ +``` + +Open the printed URL in Google Chrome or Chromium to attach to the debugger session. The inspector listens on port `9229`, which can be changed with the `NS_LINUX_INSPECTOR_PORT` environment variable. + +The app also shows up in `chrome://inspect`, and other clients that speak the Chrome DevTools Protocol, such as the VS Code JavaScript debugger, can attach to `127.0.0.1:9229`. + +The same options as on the other platforms are supported: + +- `--debug-brk` - pauses on the first line of JavaScript until the debugger connects. +- `--start` - attaches to an app that is already running with the debugger enabled (started with `ns debug linux`), without restarting it. +- `--timeout` - number of seconds the CLI waits for the inspector to start. Default is 90 seconds. + +`ns run linux` doesn't start the inspector, use `ns debug linux` instead. The debugger is only available with the default `@nativescript/linux` (V8) runtime, and it is turned off in release builds unless the [`inspector` fuse](/configuration/nativescript#linux-fuses) is enabled. + +The debugger, console, sources, CPU profiling and memory snapshots are provided by V8's inspector. In CPU profiles, time spent in C functions is shown under the C function's name. Network requests made with `@nativescript/core` HTTP APIs are shown in the **Network** tab. + +### Console output + +`ns run linux` and `ns debug linux` stream the app's standard output and standard error (including `console` calls and uncaught errors) to your terminal. Run the CLI with `--log trace` to also see when the app starts and exits, with its exit code or signal. + +An app started from the desktop's app launcher writes its output wherever the desktop session sends it, usually the systemd journal (`journalctl --user -f`). + +### Debugging native code with GDB + +To debug C code (your own [native code](/guide/native-code/linux), GTK or the runtime), attach [GDB](https://www.sourceware.org/gdb/) to the running app: + +```bash +ns run linux +# in another terminal +gdb -p $(pgrep -n ) +``` + +Native code in debug builds is compiled with `-O0 -g`, so GDB can show its sources and variables. + +To debug GTK warnings and critical messages, set `G_DEBUG=fatal-criticals` in the environment before running the CLI. The app then stops at the first critical message, and GDB shows where it came from. diff --git a/content/guide/extending-classes-and-implementing-interfaces-linux.md b/content/guide/extending-classes-and-implementing-interfaces-linux.md new file mode 100644 index 00000000..86ae0d53 --- /dev/null +++ b/content/guide/extending-classes-and-implementing-interfaces-linux.md @@ -0,0 +1,261 @@ +--- +title: Extending GObject classes and implementing interfaces +description: Subclass GObject and GTK classes and implement GObject interfaces from JavaScript on Linux. +contributors: + - triniwiz +--- + +::: warning Experimental +The Linux platform is experimental. See [Developing for Linux](/guide/linux/). +::: + +On Linux you can extend GObject classes (for example `GObject`, `GtkWidget` or `GtkButton`) and implement GObject interfaces (for example `GListModel` or `GtkOrientable`), with GObject properties and signals. When native code calls a virtual function (vfunc) or interface method you implement, your JavaScript runs. Members you don't override keep their native behavior. + +## Extending classes + +Extend the struct of a GObject class with the `class` syntax. Methods with the name of a vfunc of the class (or of one of its parents) override it: + +```ts +class Swatch extends GtkWidget { + constructor() { + super() + this.color = new GdkRGBA({ red: 1, alpha: 1 }) + } + + measure( + orientation, + forSize, + minimum, + natural, + minimumBaseline, + naturalBaseline, + ) { + new interop.Reference(interop.types.int32, minimum).value = 48 + new interop.Reference(interop.types.int32, natural).value = 96 + } + + snapshot(snapshot) { + const rect = new graphene_rect_t({ + origin: { x: 0, y: 0 }, + size: { + width: gtk_widget_get_width(this), + height: gtk_widget_get_height(this), + }, + }) + gtk_snapshot_append_color(snapshot, this.color, rect) + } +} + +const swatch = new Swatch() +gtk_box_append(box, swatch) +``` + +```ts +class CountingButton extends GtkButton { + constructor(label) { + super() + this.clicks = 0 + gtk_button_set_label(this, label) + } + + // GtkButtonClass.clicked, the class handler of the "clicked" signal + clicked() { + this.clicks++ + } +} +``` + +- Vfuncs are named as in the class structs of the C headers (`GtkWidgetClass.snapshot`, `GObjectClass.constructed`), in snake_case or camelCase (`size_allocate` or `sizeAllocate`). Vfuncs receive the instance as `this`, followed by their other arguments. +- `super.method(...)` calls the parent's implementation, a JavaScript one or the native one. +- An instance is passed to C functions as the GObject (`gtk_box_append(box, swatch)`), and fields you set on `this` are visible when native code calls your overrides. +- `instanceof` works for your class and its native parents. +- Classes can be extended again, from JavaScript. +- `MyClass.$gtype` is the GType registered for the class, named after it (or after a `static GTypeName`). + +Or call `extend` on the class, optionally passing the name of the GType first: + +```ts +const Swatch = GtkWidget.extend('Swatch', { + init() { + // called after construction, with the constructor arguments + this.color = new GdkRGBA({ red: 1, alpha: 1 }) + }, + snapshot(snapshot) { + this.super.snapshot(snapshot) + }, +}) +``` + +::: warning Final classes +Only derivable classes can be extended. Classes declared final in C (for example `GListStore`) can't, and constructing a subclass of one throws. +::: + +### TypeScript and `@NativeClass()` + +Code written for Android and iOS works unchanged: `@NativeClass()` classes and TypeScript's ES5 output are supported. On Linux the decorator isn't required. + +```ts +@NativeClass() +class CountingButton extends GtkButton { + declare clicks: number + + clicked() { + this.clicks = (this.clicks ?? 0) + 1 + } +} +``` + +### Construction + +`constructed` and other vfuncs that GObject calls while it creates an instance run before the constructor's code after `super()`, as they do in C. + +The argument passed to `super()` sets GObject properties when it is a plain object, as `g_object_new` does. A class without a constructor passes its argument on: + +```ts +const item = new TodoItem({ title: 'Write docs', done: false }) +``` + +Instances created by native code, for example by `GtkBuilder` or by `g_object_new`, get a JavaScript object when JavaScript first needs one, and the class's constructor runs for them without arguments. + +## Properties + +Declare GObject properties in a static `properties` object. They are JavaScript properties of the instances, and GObject properties for native code (`g_object_get`, property bindings, `GtkBuilder`, `notify::` signals): + +```ts +class TodoItem extends GObject { + static properties = { + title: { type: G_TYPE_STRING, default: '' }, + done: { type: G_TYPE_BOOLEAN, default: false }, + priority: { type: G_TYPE_INT, minimum: 0, maximum: 10, default: 5 }, + } +} + +const item = new TodoItem({ title: 'Write docs' }) +g_signal_connect(item, 'notify::done', () => console.log('done changed')) +item.done = true // notifies +``` + +| Option | Description | +| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` | A `G_TYPE_*` constant, a GType, a GObject class (`GtkWidget` or a JavaScript class), or an enum, flags or boxed type. Default `G_TYPE_STRING`. | +| `default` | The default value. | +| `minimum`, `maximum` | The range of numeric properties. | +| `flags` | `GParamFlags`, default `GParamFlags.READWRITE`. | +| `nick`, `blurb` | The property's nickname and description. | +| `override` | Implements a property declared by an interface or a parent class, see [Implementing interfaces](#implementing-interfaces). | + +The GObject name of a property is its key in kebab-case: `itemCount` is the `item-count` property. To compute a property, define a getter and setter for it in the class. Call `g_object_notify(this, 'name')` when its value changes. + +## Signals + +Declare signals in a static `signals` object, and emit them with `emit`: + +```ts +class Timer extends GObject { + static signals = { + tick: { params: [G_TYPE_INT] }, + 'time-up': {}, + } + + start(seconds) { + let left = seconds + g_timeout_add( + 1000, + () => { + this.emit('tick', --left) + if (left > 0) return 1 + this.emit('time-up') + return 0 + }, + null, + ) + } +} + +const timer = new Timer() +g_signal_connect(timer, 'tick', (self, left) => console.log(left)) +``` + +| Option | Description | +| ------------ | --------------------------------------------------------------------- | +| `params` | The parameter types: `G_TYPE_*` constants, GTypes or GObject classes. | +| `returnType` | The return type, default `G_TYPE_NONE`. | +| `flags` | `GSignalFlags`, default `GSignalFlags.RUN_LAST`. | + +`emit` converts the arguments to the signal's parameter types and returns the signal's return value. It works for every signal of the instance, including those of native parent classes. + +## Implementing interfaces + +List the interfaces a class implements in a static `interfaces` array, or with `@Interfaces`. Methods with the name of an interface method implement it: + +```ts +class TodoList extends GObject { + static interfaces = [GListModelInterface] + + constructor() { + super() + this.items = [] + } + + getItemType() { + return TodoItem.$gtype + } + + getNItems() { + return this.items.length + } + + getItem(position) { + const item = this.items[position] + // get_item returns a new reference (transfer full) + return item ? g_object_ref(item) : null + } + + add(item) { + this.items.push(item) + g_list_model_items_changed(this, this.items.length - 1, 0, 1) + } +} + +const list = new TodoList() +const selection = gtk_no_selection_new(list) +``` + +An interface is given as its interface struct (`GListModelInterface`, `GtkOrientableIface`), its GType (`g_list_model_get_type()`) or its type name (`'GListModel'`). + +Properties declared by an interface are implemented with `override`: + +```ts +@Interfaces([GtkOrientableIface]) +class Strip extends GtkWidget { + static properties = { orientation: { override: true } } + + constructor() { + super() + this.orientation = GtkOrientation.HORIZONTAL + } +} +``` + +Values and objects returned to C follow the C function's ownership rules, as for any [callback](/guide/linux-marshalling#callbacks): return a new reference where the C API expects one (`transfer full`), as `getItem` above does. + +## Lifetime + +An instance of a JavaScript class is a JavaScript object together with the GObject it drives. The JavaScript object holds a toggle reference on the GObject: + +- While native code also references the GObject (for example a parent widget or a `GListStore`), the JavaScript object is kept alive, with its fields. +- When only JavaScript references it, the JavaScript object can be garbage collected, which releases the GObject. + +You don't need to unref instances you create with `new`. Use `dispose` (not `finalize`) to release resources, and call `super.dispose()`. + +Signal arguments, property values and vfunc instances that are instances of a JavaScript class are passed to JavaScript as their JavaScript object. C functions that return them return an `interop.Pointer` to the GObject. To compare it with an instance, use `interop.handleof(instance).equals(pointer)`. + +## How it works + +The runtime registers a GType for each class the first time it is constructed, deriving from the GType of the class it extends. The vtable slots of the methods you override, and the interface methods you implement, call into your JavaScript object. No build step or generated code is involved. + +## Limitations + +- Final classes can't be extended. +- `finalize` can't be overridden, use `dispose`. +- GTK classes can only be used on the main thread. Native code that calls an override from another thread waits while it runs on the JavaScript thread, see [Multithreading › Linux](/guide/multithreading#linux). diff --git a/content/guide/linux-marshalling.md b/content/guide/linux-marshalling.md new file mode 100644 index 00000000..30291164 --- /dev/null +++ b/content/guide/linux-marshalling.md @@ -0,0 +1,300 @@ +--- +title: Linux Marshalling +description: How JavaScript values are converted to and from C types on Linux. +contributors: + - triniwiz +--- + +::: warning Experimental +The Linux platform is experimental. See [Developing for Linux](/guide/linux/). +::: + +On Linux, NativeScript calls C libraries directly. The runtime reads the signatures of C functions, structs, enums and constants from metadata generated from the C headers, and converts JavaScript values to C values (and back) on every call. + +## Functions, enums and constants + +C functions, enums, constants and global variables are JavaScript globals with the same name as in C. They are resolved from the metadata the first time they are used: + +```ts +const pid = getpid() +const home = g_get_home_dir() // a JavaScript string + +const box = gtk_box_new(GtkOrientation.VERTICAL, 8) +gtk_box_append(box, gtk_label_new('Hello')) +``` + +Enum members can be used with the enum's prefix removed (`GtkOrientation.VERTICAL`), with their full C name (`GLogLevelFlags.G_LOG_LEVEL_WARNING`), or as a global (`G_LOG_LEVEL_WARNING`). Enums are also reverse mapped, `GLogLevelFlags[16]` returns the member's name. + +Function-like C macros (for example `G_OBJECT()` or `GTK_WIDGET()`) aren't available. Casts between pointer types aren't needed in JavaScript, pass the pointer as it is. + +Calls are checked against the C signature: passing the wrong number of arguments, or a value that can't be converted, throws a `TypeError`. + +## Primitive types + +| C type | JavaScript to C | C to JavaScript | +| --------------------------------------------------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------------- | +| `char`, `short`, `int` (signed and unsigned), enums | `number` (truncated to the C type's size) | `number` | +| `long`, `long long`, `int64_t`, `size_t`, `gsize`, `GType`, ... | `number` or `bigint` | `number`, or `bigint` for values outside ±253 | +| `float`, `double`, `long double` | `number` | `number` | +| `bool` (`_Bool`) | any value, converted to `true` or `false` | `boolean` | +| `gboolean` | `number` or `boolean` | `number` (`0` or `1`), because `gboolean` is a C `int` | +| `const char *` | `string`, `interop.Pointer` or `null` | `string`, or `null` for `NULL` | +| `char *` | `string`, `interop.Pointer` or `null` | `interop.Pointer`, see [Strings](#strings) | +| pointers (`void *`, `GtkWidget *`, ...) | `interop.Pointer`, `interop.Reference`, struct, `ArrayBuffer`, typed array or `null` | `interop.Pointer`, or `null` for `NULL` | +| function pointers | a JavaScript function, see [Callbacks](#callbacks) | a callable JavaScript function | +| structs and unions (by value) | a struct instance or a plain object | a struct instance, see [Structs](#structs) | + +`null` and `undefined` are passed as `NULL` (or `0` for numbers). + +64-bit integers are returned as a `number` when the value fits, and as a `bigint` otherwise, so code that reads them should handle both: + +```ts +atoll('42') // 42 +atoll('9007199254740993') // 9007199254740993n +``` + +## Pointers + +Pointers are `interop.Pointer` objects. Every call returns a new object, so compare pointers with `equals()`, not `===`: + +```ts +const box = gtk_box_new(GtkOrientation.VERTICAL, 0) +const label = gtk_label_new('Hello') +gtk_box_append(box, label) + +gtk_widget_get_parent(label).equals(box) // true + +box.toHexString() // '0x5581f3a0c2d0' +box.add(8) // a pointer 8 bytes further +new interop.Pointer(0x1000) // a pointer from an address +``` + +To allocate memory, use `interop.alloc`. The memory is zeroed and freed when the returned pointer is garbage collected: + +```ts +const buffer = interop.alloc(64) +``` + +`interop.sizeof()` returns the size of a type or struct in bytes, and `interop.handleof()` returns the address of a reference, struct, typed array or `ArrayBuffer`. + +### Memory owned by C + +Memory returned by C functions isn't managed by JavaScript. Free it the same way you would in C, or use `interop.adopt` to free it automatically when the pointer is garbage collected: + +```ts +const text = g_strdup('hello') +// ... +g_free(text) + +// freed with g_free once `owned` is garbage collected +const owned = interop.adopt(g_malloc0(64), g_free) +``` + +The free function must be a native function (such as `free`, `g_free` or `g_object_unref`). It is called from the event loop after garbage collection, not during it. + +## Strings + +JavaScript strings passed to `char *` and `const char *` parameters are converted to a temporary UTF-8 copy, which is only valid during the call. To pass a string that the C code keeps, create it with `interop.cstring()`, which returns a pointer to a copy that lives as long as the pointer: + +```ts +const name = interop.cstring('my-app') +``` + +`const char *` results are returned as JavaScript strings. `char *` results are returned as pointers, because the caller usually owns (and has to free) the memory. Use `interop.stringFromCString()` to read them: + +```ts +const path = g_build_filename('/tmp', 'file.txt', null) +console.log(interop.stringFromCString(path)) // '/tmp/file.txt' +g_free(path) +``` + +## Typed arrays and buffers + +An `ArrayBuffer`, typed array or `DataView` can be passed to any pointer parameter. The C function receives the address of its data, without a copy: + +```ts +const nums = new Int32Array([3, 1, 2]) +qsort(nums, nums.length, 4, (a, b) => int(a) - int(b)) +console.log(nums) // [1, 2, 3] +``` + +To read or write C memory from JavaScript without a copy, create an `ArrayBuffer` over it with `interop.bufferFromData()`. The optional third argument is a native function that frees the memory when the buffer is garbage collected: + +```ts +const mem = g_malloc0(8) +const bytes = new Uint8Array(interop.bufferFromData(mem, 8, g_free)) +memset(mem, 7, 4) +console.log(bytes[0]) // 7 +``` + +## References and out parameters + +`interop.Reference` holds a C value in memory, and is passed as a pointer to it. Use it for out parameters: + +```ts +const src = interop.cstring('42rest') +const end = new interop.Reference(interop.types.pointer, null) + +strtol(src, end, 10) // 42 +interop.stringFromCString(end.value) // 'rest' +``` + +```ts +const error = new interop.Reference(interop.types.pointer, null) +const contents = new interop.Reference(interop.types.pointer, null) + +if (!g_file_get_contents('/etc/hostname', contents, null, error)) { + console.log( + interop.stringFromCString( + new interop.Reference(GError, error.value).value.message, + ), + ) +} +``` + +`new interop.Reference(type, value)` allocates memory for one value of `type`. When `value` is a pointer, the reference reads and writes the memory at that pointer instead, which is how you read a value a pointer points to: + +```ts +const int = (p) => new interop.Reference(interop.types.int32, p).value +``` + +The available types are `interop.types.bool`, `int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`, `int64`, `uint64`, `long`, `ulong`, `float`, `double`, `longdouble`, `unichar`, `UTF8CString` (`char *`), `pointer` (`void *`) and `void`. Struct constructors can be used as types too. + +A reference only has a `value` property. To read the elements of a C array, move the pointer with `add()`: + +```ts +const third = new interop.Reference(interop.types.int32, array.add(2 * 4)).value +``` + +## Structs + +C structs and unions are globals named after their typedef (`GdkRGBA`, `graphene_rect_t`, `GList`). The struct tag (`_GdkRGBA`) is the same constructor. + +```ts +const color = new GdkRGBA({ red: 1, green: 0.5, blue: 0, alpha: 1 }) +color.alpha = 0.5 + +const time = new tm() // zero filled +gmtime_r(new interop.Reference(interop.types.long, 0), time) +console.log(time.tm_year) // 70 +``` + +Struct fields read and write the struct's memory directly. Nested structs are views into the same memory: + +```ts +const timer = new itimerval() +timer.it_value.tv_sec = 5 +timer.it_interval = { tv_sec: 2, tv_usec: 0 } +``` + +- Structs returned by value are copied into memory owned by JavaScript. +- A struct instance passed to a pointer parameter is passed by reference, so the C function writes into it. +- A plain object can be passed instead of a struct instance. Its fields are copied in and, for pointer parameters, copied back after the call. Missing fields are zero. +- Fields are not own properties, so `Object.keys()` and spreading return nothing. Use `toJSON()` (or `JSON.stringify()`) to get a plain object. +- Pointer fields are read as `interop.Pointer`. To view a struct that a pointer points to, use `new interop.Reference(StructType, pointer).value`. + +```ts +const label = g_object_ref_sink(gtk_label_new('Hello')) +const object = new interop.Reference(GObject, label).value +console.log(object.ref_count) // 1 +``` + +## Callbacks + +A JavaScript function can be passed to any function pointer parameter. The runtime generates a C function with the parameter's signature that calls it: + +```ts +const nums = new Int32Array([4, 2, 8, 6]) +qsort(nums, 4, 4, (a, b) => { + return ( + new interop.Reference(interop.types.int32, a).value - + new interop.Reference(interop.types.int32, b).value + ) +}) + +g_timeout_add( + 1000, + () => { + console.log('tick') + return 1 // G_SOURCE_CONTINUE + }, + null, +) +``` + +C functions can be passed as callbacks too, for example `qsort(array, count, size, strcmp)`. + +For parameters with a generic type, such as `GCallback`, create the C function with an explicit signature using `interop.callback()`: + +```ts +const compare = interop.callback((a, b) => int(a) - int(b), { + returns: interop.types.int32, + args: [interop.types.pointer, interop.types.pointer], +}) +``` + +Keep the following in mind: + +- The C function generated for a JavaScript function stays valid until you call `interop.release(fn)`, so it is safe to use for callbacks that are called later (signal handlers, timeouts and idle callbacks). Release callbacks you no longer need. +- A string returned from a callback is only valid until the callback returns. Return `interop.cstring()` (and keep a reference to it) or `g_strdup()` instead. +- An exception thrown in a callback doesn't reach the C caller. It is reported as an uncaught error, and the C caller receives `0` (or `NULL`). +- Callbacks always run on the JavaScript thread that created them, see [Threading](#threading). + +## GObject signals + +`g_signal_connect` and `g_signal_connect_after` connect a JavaScript function to a GObject signal. The handler receives the instance, the signal's arguments and the user data: + +```ts +g_signal_connect(button, 'clicked', (self) => { + console.log('clicked') +}) + +g_signal_connect(toggle, 'notify::active', (self) => { + console.log(gtk_switch_get_active(self)) +}) +``` + +The argument types are read from the signal's definition. String arguments are passed as pointers, read them with `interop.stringFromCString()`. + +## GObject memory management + +GObjects are `interop.Pointer` values, and their reference counts are not managed by the runtime. Follow the [GObject ownership rules](https://docs.gtk.org/gobject/concepts.html#object-memory-management) as you would in C: unref objects you own when you are done with them, or adopt them so they are unreffed when the pointer is garbage collected: + +```ts +const label = interop.adopt( + g_object_ref_sink(gtk_label_new('hi')), + g_object_unref, +) +``` + +Widgets added to a parent widget are owned by the parent and don't need to be unreffed. + +Instances of [JavaScript GObject classes](/guide/extending-classes-and-implementing-interfaces-linux) are managed for you: their JavaScript object holds a reference on the GObject. + +## Variadic functions + +The C types of the variadic arguments of functions such as `printf` are chosen from the JavaScript values: + +| JavaScript value | C type | +| --------------------------------- | ------------------------------------------------------- | +| integer `number`, `boolean` | `int` (`unsigned int` or `long long` if it doesn't fit) | +| non-integer `number` | `double` | +| `bigint` | `long long` | +| `string` | `char *` | +| `interop.Pointer`, struct, `null` | pointer | + +```ts +const buffer = interop.alloc(64) +snprintf(buffer, 64, '%s|%d|%.2f|%lld', 'x', -7, 0.5, 1234567890123n) +interop.stringFromCString(buffer) // 'x|-7|0.50|1234567890123' +``` + +A whole number is always passed as an integer, so `printf('%f', 2)` doesn't print `2.000000`. Format whole numbers with `%d`, and pass a `bigint` to `%lld` and `%ld`, as small numbers are passed as a 32-bit `int`. + +## Threading + +JavaScript runs on the main thread, which runs the GLib main loop together with GTK. `setTimeout`, promises and GTK signals all run on that loop. + +When C code calls a JavaScript callback from another thread, the call is queued to the JavaScript thread, and the C thread waits until the callback has run. Don't block the JavaScript thread waiting for such a thread (for example with `g_thread_join`), as that deadlocks. + +[Workers](/guide/multithreading#linux) have their own thread and GLib main context, and can call C APIs, but not GTK. See [Multithreading › Linux](/guide/multithreading#linux). diff --git a/content/guide/linux/index.md b/content/guide/linux/index.md new file mode 100644 index 00000000..84771115 --- /dev/null +++ b/content/guide/linux/index.md @@ -0,0 +1,209 @@ +--- +title: Developing for Linux +description: Build native Linux desktop apps with NativeScript and GTK 4. +contributors: + - triniwiz +--- + +NativeScript can target the Linux desktop in addition to Android and iOS. Your app runs on a V8 based runtime that calls C libraries directly from JavaScript: GTK 4, GLib/GIO, Pango, Cairo, libc, X11, EGL/OpenGL ES and more, plus any C library you add yourself. `@nativescript/core` UI components render as native [GTK 4](https://www.gtk.org/) widgets. + +::: warning Experimental +The Linux platform is experimental. APIs, tooling and requirements may change between releases, and some features available on Android and iOS are not implemented yet (see [Known limitations](#known-limitations)). Please report issues on [GitHub](https://github.com/NativeScript/NativeScript/issues) or in [our Community Discord](https://nativescript.org/discord). +::: + +## Requirements + +- A Linux development machine, x64 or arm64, with glibc 2.28 or newer (most distributions released since 2018). Linux apps can only be built on Linux. +- GTK 4. + +Follow the [Linux setup guide](/setup/linux#setting-up-linux-for-linux-desktop) to prepare your environment. + +## Adding Linux to a project + +The Linux runtime is distributed as the [`@nativescript/linux`](https://www.npmjs.com/package/@nativescript/linux) package. While the platform is in preview, add it using the `alpha` tag: + +```bash +ns platform add linux@alpha +``` + +This adds `@nativescript/linux` to the `devDependencies` of your `package.json`. You can also install it directly: + +```bash +npm install --save-dev @nativescript/linux@alpha +``` + +The CLI creates the app in `platforms/linux//` the first time you prepare, build or run the app for Linux. If the project has no `App_Resources/Linux` folder, the CLI adds the default one. + +::: tip +Linux support requires a version of `@nativescript/core` that includes the Linux implementation. Make sure your project uses the latest `@nativescript/core` and `@nativescript/webpack` or `@nativescript/vite`. +::: + +### JavaScript engines + +`@nativescript/linux` runs your app on V8. The runtime is also available on other JavaScript engines, as separate packages. Add exactly one of them to `devDependencies` instead of `@nativescript/linux`, and the CLI uses it: + +| Package | Engine | +| ----------------------------- | ----------------------------------------------------------------- | +| `@nativescript/linux` | V8 (recommended) | +| `@nativescript/linux-quickjs` | [QuickJS](https://github.com/quickjs-ng/quickjs) | +| `@nativescript/linux-v8` | V8, over Node-API | +| `@nativescript/linux-hermes` | [Hermes](https://github.com/facebook/hermes) | +| `@nativescript/linux-jsc` | The system's JavaScriptCore (requires WebKitGTK's JavaScriptCore) | + +The other engines have the same C interop as `@nativescript/linux`, but don't support the debugger (`ns debug linux`). + +## Running the app + +```bash +ns run linux +``` + +The app is built and launched on the local machine. Console output is streamed to your terminal, and file changes are synced to the running app. With the [Vite bundler](/configuration/vite) changes are applied using HMR; with webpack the app restarts on each change. + +See [Running on Linux](/guide/running#running-on-linux) and [Debugging on Linux](/guide/debugging#debugging-on-linux) for more details. + +## Writing platform-specific code + +Linux follows the same conventions as the other platforms. + +### Platform files + +Files ending in `.linux.ts` (also `.linux.js`, `.linux.css`, `.linux.scss`, ...) are only included in Linux builds: + +``` +my-component/ +├── index.android.ts +├── index.ios.ts +└── index.linux.ts +``` + +::: warning Note +Linux builds only pick up `.linux.*` files, they never fall back to `.ios.*` or `.android.*`. If a module has platform files, add a `.linux.*` variant (or a shared file without a platform suffix). +::: + +### Platform conditionals + +The `__LINUX__` global is replaced at build time, so the code for other platforms is removed from the bundle: + +```ts +if (__LINUX__) { + // Linux only +} +``` + +At runtime you can also check `Device.os`, or import `isLinux`: + +```ts +import { Device, isLinux } from '@nativescript/core' + +console.log(Device.os) // 'Linux' +console.log(isLinux) // true +``` + +### Platform specific markup and CSS + +```xml + + + + +``` + +```css +.ns-linux .title { + font-size: 24; +} +``` + +## Accessing native APIs + +C functions, enums, constants and structs from the [Linux SDK](#the-linux-sdk) are available as globals, named exactly as in their C headers: + +```ts +console.log(g_get_user_name()) +console.log(getpid()) + +const label = gtk_label_new('Hello from JavaScript') +gtk_label_set_selectable(label, true) +``` + +Every `@nativescript/core` view exposes the underlying GTK widget (a `GtkWidget *`) through `nativeView`: + +```ts +import { Button } from '@nativescript/core' + +const button = new Button() +// ...once the view is loaded +gtk_widget_set_tooltip_text(button.nativeView, 'Click me') +``` + +GTK signals are connected with `g_signal_connect`: + +```ts +g_signal_connect(button.nativeView, 'clicked', () => { + console.log('clicked') +}) +``` + +Read [Linux Marshalling](/guide/linux-marshalling) to learn how JavaScript values are converted to C types, how to work with pointers, structs and callbacks, and how to manage the memory of GObjects. + +GObject classes, such as GTK widgets, can be extended from JavaScript, and GObject interfaces implemented. See [Extending GObject classes and implementing interfaces](/guide/extending-classes-and-implementing-interfaces-linux). + +### The Linux SDK + +The runtime ships metadata and TypeScript typings for the following libraries: + +| Module | Libraries | +| -------------- | ---------------------------------------------------------------------------------------- | +| `libc` | The C standard library and POSIX (`libc`, `libm`) | +| `GLib` | GLib, GObject, GIO | +| `Gtk` | GTK 4 and GDK (including `gdk/x11`), with Pango, Cairo, GdkPixbuf, Graphene and HarfBuzz | +| `X11` | Xlib | +| `EGL`, `GLES` | EGL, OpenGL ES 2 and 3 | +| `fontconfig` | Fontconfig, PangoFT2 | +| `libxml2` | libxml2 | +| `icu` | ICU (`icu-uc`) | +| `zlib`, `uuid` | zlib, libuuid | + +Run `ns typings linux` to add the typings to your project, see [Generating TypeScript types](/guide/native-code/generate-typings#generate-types-for-linux). Other C libraries can be added to an app or plugin, see [Adding Linux native code](/guide/native-code/linux). + +### Web APIs + +The runtime also provides common web APIs as globals: `fetch`, `WebSocket`, `URL`, `TextEncoder`/`TextDecoder`, `AbortController`, `structuredClone`, `crypto.getRandomValues`, `crypto.randomUUID` and `crypto.subtle`, `performance`, `requestAnimationFrame` and `Worker`. + +## App resources + +Linux specific resources (app icon, desktop entry and AppStream metadata) live in `App_Resources/Linux`. See [App_Resources › Linux](/project-structure/app-resources#linux-specific-resources). + +## Using plugins + +Plugins written purely in JavaScript/TypeScript on top of `@nativescript/core` generally work on Linux. Plugins with native code need a Linux implementation (`.linux.ts` files and, optionally, C/C++ code in `platforms/linux`). See [Adding Linux native code](/guide/native-code/linux#plugins). + +## Publishing + +Release builds produce a `.tar.gz` archive with an install script. See [Distributing Linux apps](/guide/publishing/linux). + +## Known limitations + +The following are not implemented on Linux yet: + +- `WebView` doesn't embed web content. It shows the URL and a button that opens it in the default browser. +- Accessibility properties (`accessible`, `accessibilityLabel`, ...) +- Page navigation transitions and shared element transitions (navigation isn't animated) +- `box-shadow`, `background-image` (including gradients) and `clip-path`. View backgrounds support `background-color`, borders and `border-radius`. +- `hint` and `maxLength` on TextView +- `font://` image sources, Image `tintColor`, and cropping with `stretch="aspectFill"` +- Custom fonts in `src/fonts`. Fonts have to be installed on the system. +- Tab icons in TabView + +Some behaviors differ from mobile: + +- `Screen.mainScreen` reports the size of the app **window**, not the monitor, and `Device.deviceType` reports `Tablet` or `Phone` based on the window size. +- Size qualifiers (for example `page.minWH600.xml`) are re-evaluated when the window is resized. +- `Connectivity` reports `ethernet` or `none`, it can't tell Wi-Fi from other connections. +- `getLocationOnScreen()` returns coordinates relative to the window, because Wayland doesn't expose window positions. +- The `ActionBar` is drawn inside the page, below the window's title bar. +- Dialogs and modal views open as separate windows. +- In release builds the app folder is read-only. Write files to `knownFolders.documents()` (`~/.local/share/`) or `knownFolders.temp()` (`~/.cache/`). `ApplicationSettings` are stored in `~/.config//settings.json`. diff --git a/content/guide/marshalling/index.md b/content/guide/marshalling/index.md index 12a6c503..b6b524e6 100644 --- a/content/guide/marshalling/index.md +++ b/content/guide/marshalling/index.md @@ -2,11 +2,12 @@ title: Marshalling in NativeScript --- -Marshalling in NativeScript refers to the conversion of JavaScript data types to native platform language (Swift/Objective C and Kotlin/Java) data types and vice versa. +Marshalling in NativeScript refers to the conversion of JavaScript data types to native platform language (Swift/Objective C, Kotlin/Java and C on Linux) data types and vice versa. -The conversion is handled implicitly by the NativeScript iOS and Android runtimes. +The conversion is handled implicitly by the NativeScript iOS, Android and Linux runtimes. For more information about how NativeScript converts data types for each platform, read the following articles: - [iOS Marshalling](/guide/ios-marshalling) -- [Android Marshalling](/guide/android-marshalling) \ No newline at end of file +- [Android Marshalling](/guide/android-marshalling) +- [Linux Marshalling](/guide/linux-marshalling) diff --git a/content/guide/metadata.md b/content/guide/metadata.md index 6474743c..4978697f 100644 --- a/content/guide/metadata.md +++ b/content/guide/metadata.md @@ -133,3 +133,18 @@ if (android.os.Build.VERSION.SDK_INT >= 21) { ## iOS Metadata This is our own custom data format for listing the iOS APIs we are aware of and can handle. It stores the minimal required information and provides a small size and highly efficient read access. iOS supports type introspection to some extent but along with the C APIs embedded all the way in the native APIs we had to store a lot of extra information. The Metadata is pre-generated at compile time from the SDK header files and embedded in the app package (ipa). + +## Linux Metadata + +::: warning Experimental +The Linux platform is experimental. See [Developing for Linux](/guide/linux/). +::: + +On Linux, the runtime uses the same kind of metadata as on iOS. It is generated from C headers by a Clang based metadata generator, and describes the C functions, structs, unions, enums, constants and global variables of each library, along with the shared library the runtime loads them from. + +- The metadata of the [Linux SDK](/guide/linux/#the-linux-sdk) is generated ahead of time and ships with the runtime. +- The metadata of the app's and plugins' [native code](/guide/native-code/linux) is generated by the CLI on every build, and written to the app's `metadata/` folder. + +The [metadata filtering rules](#metadata-filtering) apply to the SDK metadata on Linux too: add a `native-api-usage.json` to `App_Resources/Linux` or to a plugin's `platforms/linux` folder. The metadata of native code is always kept, but honors the `blacklist` rules. + +Names are resolved from the metadata the first time JavaScript uses them. A C function whose library can't be loaded resolves to `undefined`. diff --git a/content/guide/multithreading.md b/content/guide/multithreading.md index e47f5fc3..caa1f189 100644 --- a/content/guide/multithreading.md +++ b/content/guide/multithreading.md @@ -360,3 +360,17 @@ There are certain limitations to keep in mind when working with workers: - **No object transferring**. If you are a web developer you may be familiar with the ArrayBuffer and MessagePort transferring support in browsers. Currently, in NativeScript there is no such concept as object transferring. - **Debugging workers is not currently possible.** - **No nested workers support**. We want to hear from the community if this is something we need to support. + +## Linux + +::: warning Experimental +The Linux platform is experimental. See [Developing for Linux](/guide/linux/). +::: + +On Linux, JavaScript runs on the main thread, which runs the GLib main loop together with GTK. Each worker runs in its own thread with its own JavaScript runtime and its own GLib main context, and has access to all C APIs, but GTK may only be used from the main thread. + +Keep the following in mind: + +- Messages are serialized using the structured clone algorithm, so values such as `Date`, `Map`, `Set`, `ArrayBuffer` and typed arrays can be sent. To send native data, copy it into an `ArrayBuffer` or send the pointer's address. +- JavaScript callbacks run on the thread that created them. When C code invokes a callback from another thread, the call is queued to the callback's JavaScript thread, and the calling thread waits until it has run. Blocking the JavaScript thread while waiting for that thread deadlocks. +- To update the UI with results from a worker, `postMessage` them to the main thread. diff --git a/content/guide/native-code/generate-typings.md b/content/guide/native-code/generate-typings.md index e50af688..6954dad6 100644 --- a/content/guide/native-code/generate-typings.md +++ b/content/guide/native-code/generate-typings.md @@ -36,10 +36,30 @@ For instance: ns typings android "com.google.android.gms:play-services-tasks" ``` +## Generate types for Linux + +::: warning Experimental +The Linux platform is experimental. See [Developing for Linux](/guide/linux/). +::: + +On a Linux machine, run: + +```bash +ns typings linux +``` + +This writes the typings for the [Linux SDK](/guide/linux/#the-linux-sdk) (GLib, GTK, Pango, Cairo, X11, ...) and for the app's and plugins' [native code](/guide/native-code/linux) to `typings/linux/`. Once the folder exists, later builds keep the typings of the native code up to date. Reference them in [references.d.ts](/project-structure/references-d-ts): + +```ts +/// +``` + +`index.d.ts` includes the typings of GLib, GTK, Pango, Cairo, GdkPixbuf, Graphene, HarfBuzz, X11 and Fontconfig. The typings of the other SDK modules (libc, ICU, EGL, OpenGL ES, libxml2, zlib and libuuid) are in `typings/linux/sdk/` too, but aren't included by default, because libc and ICU conflict with the typings of other platforms. Reference the ones you need separately, for example `/// `. + ### Custom code 1. Reference the generated types in [references.d.ts](/project-structure/references-d-ts) -2. You can now code against the platform native APIs (_strongly typed_). For various examples on how to interact with native APIs in JavaScript/TypeScript, visit the [Subclassing](/guide/subclassing/), [iOS Marshalling](/guide/ios-marshalling) and [Android Marshalling](/guide/android-marshalling) pages. +2. You can now code against the platform native APIs (_strongly typed_). For various examples on how to interact with native APIs in JavaScript/TypeScript, visit the [Subclassing](/guide/subclassing/), [iOS Marshalling](/guide/ios-marshalling), [Android Marshalling](/guide/android-marshalling) and [Linux Marshalling](/guide/linux-marshalling) pages. ## Additional Resources diff --git a/content/guide/native-code/linux.md b/content/guide/native-code/linux.md new file mode 100644 index 00000000..08a0c9b6 --- /dev/null +++ b/content/guide/native-code/linux.md @@ -0,0 +1,194 @@ +--- +title: Adding Linux native code to an application +description: Use your own C and C++ code, and other C libraries, in NativeScript Linux apps. +contributors: + - triniwiz +--- + +::: warning Experimental +The Linux platform is experimental. See [Developing for Linux](/guide/linux/). +::: + +On Linux, NativeScript calls C functions directly from JavaScript. Besides the libraries of the [Linux SDK](/guide/linux/#the-linux-sdk), an app can use its own C or C++ code, prebuilt shared libraries, or other C libraries installed on the system. + +On every build, the CLI compiles the native code, generates metadata from its headers and bundles the result with the app, as on iOS. The functions, structs, enums and constants declared in the headers are then available as JavaScript globals, see [Linux Marshalling](/guide/linux-marshalling). + +Native code for the app goes in `App_Resources/Linux`. Plugins use the same layout in their `platforms/linux` folder (see [Plugins](#plugins)): + +```bash +App_Resources/ +└─ Linux/ + ├─ src/ # C/C++ sources and headers, compiled into a shared library + ├─ include/ # public headers (optional) + ├─ lib/ + │ ├─ x64/ # prebuilt .so files (bundled) and .a files (linked) + │ └─ arm64/ + ├─ native.json # pkg-config packages, compiler and linker flags (optional) + ├─ CMakeLists.txt # builds the native code with CMake instead (optional) + └─ ... +``` + +Building native code requires a C toolchain, see [Installing a C toolchain](/setup/linux#installing-a-c-toolchain-optional). + +## Adding C or C++ code + +Put your sources (`.c`, `.cc`, `.cpp`, `.cxx`) and headers in `App_Resources/Linux/src`: + +```c +// App_Resources/Linux/src/greeter.h +#pragma once + +typedef struct { + int width; + int height; +} GreeterSize; + +const char *greeter_message(const char *name); +GreeterSize greeter_size(void); +``` + +```c +// App_Resources/Linux/src/greeter.c +#include +#include "greeter.h" + +const char *greeter_message(const char *name) { + static char buffer[128]; + snprintf(buffer, sizeof buffer, "Hello, %s!", name); + return buffer; +} + +GreeterSize greeter_size(void) { + return (GreeterSize){ 640, 480 }; +} +``` + +The sources are compiled into `libapp.so` (with `-O2` in release builds, `-O0 -g` otherwise), and every function declared in the headers can be called from JavaScript: + +```ts +console.log(greeter_message('NativeScript')) // 'Hello, NativeScript!' + +const { width, height } = greeter_size() +``` + +When the code has an `include/` folder, only the headers in `include/` are exposed to JavaScript. Otherwise every header in `src/` is. + +### Compiler flags and libraries + +To use other libraries, list their [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) packages, and any additional compiler and linker flags, in `native.json`: + +```json +{ + "pkg-config": ["gtk4", "sqlite3"], + "cflags": ["-DGREETER_DEBUG=1"], + "libs": ["-lm"] +} +``` + +The library's development package must be installed on the build machine (for example `libsqlite3-dev` for `sqlite3`), and the library itself on the machine that runs the app. + +Types your headers use from the Linux SDK (`GtkWidget *`, `GdkRGBA`, `graphene_rect_t *`, ...) are referenced by name and resolved from the SDK metadata at runtime, so a function that takes a `GtkWidget *` can be passed the `nativeView` of a NativeScript view. + +## Using prebuilt libraries + +Put prebuilt libraries in `lib/`, where `` is `x64` or `arm64`, and their headers in `include/`: + +```bash +App_Resources/ +└─ Linux/ + ├─ include/ + │ └─ mylib.h + └─ lib/ + ├─ x64/ + │ └─ libmylib.so + └─ arm64/ + └─ libmylib.so +``` + +- Shared libraries (`.so`) are bundled in the app's `lib/` folder, which the runtime searches first when it loads a library. +- Static libraries (`.a`) are linked into the compiled library. + +## Using module maps + +By default, all headers become one module named after the app (`app`) or plugin, linked to the compiled or prebuilt libraries. For more control, add a [Clang module map](https://clang.llvm.org/docs/Modules.html#module-map-language) (`module.modulemap`) to `src/` or `include/`, as on iOS. When there is one, the CLI uses it as it is: + +- The `header` entries are the headers that are exposed to JavaScript. +- The `link` entries are the libraries the runtime loads. +- Sources in `src/` are compiled into the library named by the first `link` (`link "greeter"` builds `libgreeter.so`). + +``` +module greeter [system] { + header "greeter.h" + link "greeter" +} +``` + +A module map can also expose a library that is installed on the system, with headers from anywhere: + +``` +module sqlite3 [system] { + header "/usr/include/sqlite3.h" + link "sqlite3" +} +``` + +```ts +const db = new interop.Reference(interop.types.pointer, null) +sqlite3_open(':memory:', db) +console.log(sqlite3_libversion()) +sqlite3_close(db.value) +``` + +A `link` is a library filename (`libsqlite3.so`), a soname (`libsqlite3.so.0`) or an `-l` style name (`sqlite3`). An `-l` style name is looked up as `libsqlite3.so` or `libsqlite3.so.` in the app's `lib/` folder, then on the system's library paths. If a library can't be loaded, the runtime prints the names it tried. + +## Using CMake + +For larger projects, add a `CMakeLists.txt`. It takes over the build of the native code, and `src/` is left to CMake: + +```cmake +cmake_minimum_required(VERSION 3.16) +project(greeter C) + +add_library(greeter SHARED src/greeter.c) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK4 REQUIRED IMPORTED_TARGET gtk4) +target_link_libraries(greeter PRIVATE PkgConfig::GTK4) +``` + +The CLI configures and builds the project and: + +- sets `CMAKE_BUILD_TYPE` to `Release` for `--release` builds and to `Debug` otherwise, and `NS_ARCH` to `x64` or `arm64` +- bundles every shared library the project builds in the app's `lib/` folder +- generates the metadata with the include paths and defines from the project's `compile_commands.json` + +Use a [module map](#using-module-maps) to choose the headers and libraries that are exposed to JavaScript. The CMake build folder is kept in `platforms/linux/build/native`, so later builds are incremental. + +## TypeScript typings + +Run `ns typings linux` to generate typings for your native code (and the Linux SDK) into `typings/linux`. Later builds keep them up to date. See [Generating TypeScript types](/guide/native-code/generate-typings#generate-types-for-linux). + +## Plugins + +Plugins add Linux native code in their `platforms/linux` folder, with the same layout as `App_Resources/Linux` (`src/`, `include/`, `lib//`, `native.json`, `CMakeLists.txt` and module maps): + +```bash +my-plugin/ +├─ index.linux.ts +├─ index.ios.ts +├─ index.android.ts +└─ platforms/ + └─ linux/ + ├─ src/ + │ ├─ my_plugin.c + │ └─ my_plugin.h + └─ native.json +``` + +Without a module map, the plugin's module and library are named after the package, with characters that aren't valid in C replaced by `_` (`@scope/my-plugin` becomes `scope_my_plugin` and `libscope_my_plugin.so`). + +Native code in plugins is built for the app on every build, like the app's own native code. As on the other platforms, a plugin can include a `native-api-usage.json` in `platforms/linux` to filter the metadata, see [Metadata › Metadata filtering](/guide/metadata#metadata-filtering). + +## Generating metadata by hand + +The NativeScript CLI generates metadata automatically. To use the runtime outside of the CLI, see the [Linux runtime repository](https://github.com/triniwiz/linux-runtime#metadata-for-an-apps-own-c-library) for how to run the metadata generator yourself. diff --git a/content/guide/publishing/index.md b/content/guide/publishing/index.md index 0c1c33ed..7a755d24 100644 --- a/content/guide/publishing/index.md +++ b/content/guide/publishing/index.md @@ -9,5 +9,6 @@ breadcrumbs: ## [Publishing to Google Play](/guide/publishing/android-google-play) ## [Publishing to Apple App Store](/guide/publishing/apple-app-store) +## [Distributing Linux apps](/guide/publishing/linux) ## [Publishing iOS updated using ns publish ios](/guide/publishing/ns-publish) ## [Publishing with Fastlane](https://blog.nativescript.org/automatic-nativescript-app-deployments-with-fastlane/) diff --git a/content/guide/publishing/linux.md b/content/guide/publishing/linux.md new file mode 100644 index 00000000..ff097434 --- /dev/null +++ b/content/guide/publishing/linux.md @@ -0,0 +1,111 @@ +--- +title: Distributing Linux apps +description: Build release packages of NativeScript Linux apps. +category: Publishing +categoryLink: /guide/publishing/ +prev: /guide/publishing/ +next: false +contributors: + - triniwiz +breadcrumbs: + - name: 'Publishing' + href: '/guide/publishing/' + - name: 'Distributing Linux apps' +--- + +::: warning Experimental +The Linux platform is experimental. See [Developing for Linux](/guide/linux/). +::: + +A release build packs the app into a `.tar.gz` archive that users can extract and run, or install for the current user with the included install script: + +```bash +ns build linux --release +``` + +The archive is written to `platforms/linux//dist/.tar.gz`, and the folder it was created from to `platforms/linux//dist//`. + +## Preparing the app + +### App id + +The app id names the app's desktop entry (`.desktop`), its icon and its install folder, and GTK uses it to match the app's windows to the desktop entry. It defaults to your app id ([`id`](/configuration/nativescript#id), or [`linux.id`](/configuration/nativescript#linux-id)) and must be a reverse DNS name, for example `org.nativescript.MyApp`. + +### Version + +The version shown in the app's AppStream metadata is the `version` of your `package.json`. + +### Icon, desktop entry and metadata + +Replace `App_Resources/Linux/icon.png` (512×512) or add an `icon.svg`, and update `app.desktop` and `app.metainfo.xml` with your app's name, categories, summary and description. See [App_Resources › Linux](/project-structure/app-resources#linux-specific-resources). + +### Architecture + +Each build targets a single architecture, the architecture of the build machine by default. Use `--arch` to choose `x64` or `arm64`: + +```bash +ns build linux --release --arch arm64 +``` + +Apps with [native code](/guide/native-code/linux) are compiled by the build machine's C compiler, so build them on a machine of the target architecture. + +Release packages run on glibc based distributions with glibc 2.28 or newer and GTK 4. Libraries your native code links to must be installed on the user's machine too. + +## What's in the package + +```bash +MyApp/ +├─ MyApp # the app executable +├─ app.nsbundle # the app's JavaScript and assets +├─ app.nsbundle.unpacked/ # files kept outside of the bundle (fonts by default) +├─ metadata-x86_64.bin # C API metadata +├─ metadata/ # metadata of the app's and plugins' native code +├─ lib/ # shared libraries of the app's and plugins' native code +├─ icons/ +├─ org.nativescript.MyApp.desktop +├─ org.nativescript.MyApp.metainfo.xml +└─ install.sh +``` + +### The app bundle + +By default, the app's files are packed into a single read-only `app.nsbundle`, and the executable is locked to it: + +- the bundle's integrity is checked against a hash stored in the executable +- the executable only loads the app from the bundle +- the debugger, command line flags and environment variable overrides are turned off + +These settings are [configurable](/configuration/nativescript#linux-fuses). Files that need to stay real files (fonts by default) are listed in [`linux.bundleUnpack`](/configuration/nativescript#linux-bundleunpack). + +### Source protection + +To encrypt the bundle with a key embedded in the executable, enable [`linux.sourceProtect`](/configuration/nativescript#linux-sourceprotect) or build with `--source-protect`: + +```bash +ns build linux --release --source-protect +``` + +A new key is generated for every build. To use your own, pass `--source-protect-key-hex ` or set the `NS_LINUX_BUNDLE_KEY` environment variable. + +## Installing the app + +Users extract the archive and run `install.sh`: + +```bash +tar -xzf MyApp.tar.gz +./MyApp/install.sh +``` + +The script installs the app for the current user, without root privileges: + +- the app is copied to `~/.local/share//` +- the desktop entry is written to `~/.local/share/applications/.desktop`, so the app shows up in the desktop's app launcher +- the icons are copied to `~/.local/share/icons/` + +To uninstall the app, remove those files. + +The app can also be run without installing it, by running the executable in the extracted folder. + +## Other package formats + +The CLI doesn't produce AppImage, Flatpak, Snap, `.deb` or `.rpm` packages yet. The release folder (`platforms/linux//dist//`) contains everything the app needs, including an AppStream `metainfo.xml` and a desktop entry, and can be used as the input for those packaging tools. diff --git a/content/guide/running.md b/content/guide/running.md index 9b421cc6..3c85e684 100644 --- a/content/guide/running.md +++ b/content/guide/running.md @@ -14,6 +14,7 @@ To run a project, use the `ns run` command. There's also a `ns debug` command co ```bash ns run android ns run ios +ns run linux # experimental, Linux host only ``` The `run` command runs the app on all connected devices matching the platform. You can control which devices to run on with the following flags: @@ -121,6 +122,30 @@ The app should install and launch on the iOS device. Xcode network devices sometimes get disconnected or have an unreliable connection. We recommend using a wired connection to avoid these occasional issues. ::: +## Running on Linux + +::: warning Experimental +The Linux platform is experimental. See [Developing for Linux](/guide/linux/) for the current status. +::: + +Linux apps run on the local machine, there is no emulator or remote device involved. When running on a Linux host with the [Linux environment set up](/setup/linux#setting-up-linux-for-linux-desktop), the local machine is listed as a `Linux` device: + +```bash +ns devices linux +``` + +To build and launch the app: + +```bash +ns run linux +``` + +The app runs as a child process of the CLI. Its console output is streamed to the terminal, and the app is closed when the CLI exits. The debug build is in `platforms/linux//bin/`, and the CLI also adds it to the desktop's app launcher (as `~/.local/share/applications/.desktop`), so that the app shows its name and icon. + +Changes to your app are synced to the build folder. With the [Vite bundler](/configuration/vite) changes are applied with HMR, with webpack the app reloads on every change. + +`ns run linux --release` runs a [release build](/guide/publishing/linux). + ## Running on virtual devices ### Android Emulators diff --git a/content/guide/styling.md b/content/guide/styling.md index 7c36e44a..8e99a112 100644 --- a/content/guide/styling.md +++ b/content/guide/styling.md @@ -157,6 +157,10 @@ There are 4 primary ways to target styles at iOS or Android: /// +::: tip Linux +The same conventions apply to the [Linux platform](/guide/linux/): `.linux.css` stylesheets, ` ... ` markup blocks, `linux:` attributes and `.ns-linux` CSS rules. +::: + The most common and maintainable pattern for managing platform-agnostic and platform-specific styles in NativeScript is with multiple stylesheets and CSS imports. /// flavor plain @@ -598,7 +602,7 @@ To allow for flexible styling and theming, NativeScript provides the following C - `.ns-root` - a class assigned to the application root view - `.ns-modal` - a class assigned to the modal root view -- `.ns-android`, `.ns-ios` - classes that specify the application platform +- `.ns-android`, `.ns-ios`, `.ns-linux` - classes that specify the application platform - `.ns-phone`, `.ns-tablet` - classes that specify the device type - `.ns-portrait`, `.ns-landscape`, `.ns-unknown` - classes that specify the application orientation - `.ns-light`, `.ns-dark` - classes that specify the system appearance. diff --git a/content/guide/subclassing/index.md b/content/guide/subclassing/index.md index d5296e03..2740c1e0 100644 --- a/content/guide/subclassing/index.md +++ b/content/guide/subclassing/index.md @@ -2,12 +2,13 @@ title: Extending native classes and implementing interfaces in NativeScript --- -In NativeScript, you can extend native classes and implement interfaces for Android, and conform to iOS protocols. +In NativeScript, you can extend native classes and implement interfaces for Android, conform to iOS protocols, and extend GObject classes and implement GObject interfaces on Linux. See the following articles for examples: - [Extending Kotlin/Java classes in NativeScript](/guide/extending-classes-and-implementing-interfaces-android#extending-javakotlin-classes-in-nativescript) - [Implementing Kotlin/Java interfaces in NativeScript](/guide/extending-classes-and-implementing-interfaces-android#implementing-java-kotlin-interfaces-in-nativescript) - [Extending Objective-C/Swift classes in NativeScript](/guide/extending-classes-and-conforming-to-protocols-ios.md#extending-ios-classes) - [Conforming to Objective-C/Swift protocols in NativeScript](/guide/extending-classes-and-conforming-to-protocols-ios.md#conforming-to-objective-c-swift-protocols) +- [Extending GObject classes and implementing GObject interfaces on Linux](/guide/extending-classes-and-implementing-interfaces-linux) diff --git a/content/index.md b/content/index.md index 9ad08e39..811ea339 100644 --- a/content/index.md +++ b/content/index.md @@ -16,6 +16,7 @@ Some popular use cases: - Augmenting JavaScript projects with platform API capabilities - AndroidTV and Watch development - watchOS development +- Linux desktop development (experimental, see [Developing for Linux](/guide/linux/)) - Learning native platforms through JavaScript understanding - Exploring platform API documentation by trying APIs [directly from a web browser](https://preview.nativescript.org/) without requiring a platform development machine setup. diff --git a/content/project-structure/app-resources.md b/content/project-structure/app-resources.md index 52629bd0..159ca289 100644 --- a/content/project-structure/app-resources.md +++ b/content/project-structure/app-resources.md @@ -5,7 +5,7 @@ contributors: - NathanWalker --- -The App_Resources folder contains platform-specific resources of the application (icons, configuration files, native code, etc.). An application that supports both Android and iOS would therefore contain a subfolder for each platform. +The App_Resources folder contains platform-specific resources of the application (icons, configuration files, native code, etc.). An application that supports both Android and iOS would therefore contain a subfolder for each platform (and a `Linux` subfolder when [targeting Linux](/guide/linux/)). This page serves as a quick reference to understand how most settings in App_Resources affect the behavior and the look of a NativeScript app. @@ -211,3 +211,84 @@ For a list of available entitlements refer to [Apple's Entitlements documentatio ### Adding ObjectiveC/Swift Code to an application See [Adding ObjectiveC/Swift Code to an application](/guide/native-code/ios). + +## Linux specific resources + +::: warning Experimental +The Linux platform is experimental. See [Developing for Linux](/guide/linux/). +::: + +```bash +App_Resources/ +├─ Linux/ +│ ├─ icon.png # app icon, 512x512 (or icon.svg) +│ ├─ app.desktop # desktop entry template +│ ├─ app.metainfo.xml # AppStream metadata template +│ ├─ images/ # images used with res:// URLs +│ ├─ native-api-usage.json # optional, metadata filtering rules +│ └─ src/, include/, lib/, native.json, CMakeLists.txt # optional, native code +└─ ... more +``` + +If the project doesn't have an `App_Resources/Linux` folder, the CLI adds the default one the first time you prepare the app for Linux. + +The following tokens are replaced in `app.desktop` and `app.metainfo.xml`: + +| Token | Value | +| ----------------- | --------------------------------------------------------------------------------------------------------- | +| `__APP_ID__` | the app id ([`id`](/configuration/nativescript#id) or [`linux.id`](/configuration/nativescript#linux-id)) | +| `__APP_NAME__` | the project name | +| `__APP_VERSION__` | the `version` from `package.json` | +| `__APP_DATE__` | the build date | +| `__EXEC__` | the path of the app executable, set when the app is installed | + +### App icon + +`icon.png` is the app's icon, used for its windows and in the desktop's app launcher. Use a 512×512 PNG, or an `icon.svg`, which is used instead when present. The icon is installed as `` in the `hicolor` icon theme. + +### Desktop entry (app.desktop) + +The [desktop entry](https://specifications.freedesktop.org/desktop-entry-spec/latest/) makes the app show up in the desktop's app launcher, with its name and icon. It is written as `.desktop`: + +```ini +[Desktop Entry] +Type=Application +Name=__APP_NAME__ +Exec=__EXEC__ +Icon=__APP_ID__ +Categories=Utility; +Terminal=false +StartupWMClass=__APP_ID__ +``` + +Change `Name` to your app's display name, and set the [categories](https://specifications.freedesktop.org/menu-spec/latest/category-registry.html) the app belongs to. Keep `Exec`, `Icon` and `StartupWMClass` as they are, they are needed to launch the app and to match its windows to the desktop entry. + +### AppStream metadata (app.metainfo.xml) + +[AppStream metadata](https://www.freedesktop.org/software/appstream/docs/) describes the app for software centers (such as GNOME Software, KDE Discover and Flathub). It is written as `.metainfo.xml`: + +```xml + + + __APP_ID__ + __APP_NAME__ + A NativeScript application + +

__APP_NAME__ is a desktop application built with NativeScript and GTK 4.

+
+ CC0-1.0 + __APP_ID__.desktop + + + + +
+``` + +### Images + +Images in `App_Resources/Linux/images/` can be used from your app with `res://` URLs, for example `res://logo` loads `images/logo.png` (or `logo.svg` or `logo.jpg`). + +### Adding native code to a Linux application + +See [Adding Linux native code](/guide/native-code/linux). diff --git a/content/setup/index.md b/content/setup/index.md index 1b9a9c69..337c2c20 100644 --- a/content/setup/index.md +++ b/content/setup/index.md @@ -12,14 +12,20 @@ contributors: Select the guide relevant to your development machine and follow the steps carefully to get a clean working environment ready. -| Operating System | Android | iOS | -| ---------------- | ------------------------------------------------ | ----------------------------------------------------------------------- | -| Windows | :white_check_mark: [Setup Guide](/setup/windows) | :x: Unsupported | -| macOS | :white_check_mark: [Setup Guide](/setup/macos) | :white_check_mark: [Setup Guide](/setup/macos#setting-up-macos-for-ios) | -| Linux | :white_check_mark: [Setup Guide](/setup/linux) | :x: Unsupported | +| Operating System | Android | iOS | Linux | +| ---------------- | ------------------------------------------------ | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| Windows | :white_check_mark: [Setup Guide](/setup/windows) | :x: Unsupported | :x: Unsupported | +| macOS | :white_check_mark: [Setup Guide](/setup/macos) | :white_check_mark: [Setup Guide](/setup/macos#setting-up-macos-for-ios) | :x: Unsupported | +| Linux | :white_check_mark: [Setup Guide](/setup/linux) | :x: Unsupported | :construction: [Setup Guide](/setup/linux#setting-up-linux-for-linux-desktop) (experimental) | :::warning A note about iOS on unsupported systems A Mac is required to build projects that use native iOS code. Simpler apps can be tested using [NativeScript Preview](https://preview.nativescript.org). ::: + +:::warning A note about Linux on unsupported systems + +A Linux machine (x64 or arm64) is required to build projects for the Linux desktop target. The Linux platform is currently experimental, see [Developing for Linux](/guide/linux/). + +::: diff --git a/content/setup/linux.md b/content/setup/linux.md index 5a703bc1..b2c900b7 100644 --- a/content/setup/linux.md +++ b/content/setup/linux.md @@ -110,6 +110,89 @@ ns doctor android If you see **No issues were detected** then you have successfully set up your system. +## Setting up Linux for Linux desktop + +::: warning Experimental +The Linux platform is experimental. APIs, tooling and requirements may change between releases. See [Developing for Linux](/guide/linux/) for the current status. +::: + +NativeScript can build native Linux desktop apps with [GTK 4](https://www.gtk.org/). Apps run on the local machine. + +You will need: + +- A glibc based Linux distribution released in 2018 or later (glibc 2.28 or newer), x64 or arm64 +- GTK 4 +- Node — see [Setting up Linux for Android](#setting-up-linux-for-android) above +- The NativeScript CLI + +### Installing GTK 4 + +Most desktop distributions already ship GTK 4. If yours doesn't, install it with your package manager: + +::: code-group + +```bash [Debian / Ubuntu] +sudo apt install libgtk-4-1 +``` + +```bash [Fedora] +sudo dnf install gtk4 +``` + +```bash [Arch] +sudo pacman -S gtk4 +``` + +::: + +### Installing a C toolchain (optional) + +Apps and plugins that include their own C or C++ code (see [Adding Linux native code](/guide/native-code/linux)) also need a C compiler, `pkg-config` and, for CMake projects, `cmake`, as well as the development packages of the libraries they use: + +::: code-group + +```bash [Debian / Ubuntu] +sudo apt install build-essential pkg-config cmake libgtk-4-dev +``` + +```bash [Fedora] +sudo dnf install gcc gcc-c++ pkgconf-pkg-config cmake gtk4-devel +``` + +```bash [Arch] +sudo pacman -S base-devel cmake gtk4 +``` + +::: + +Apps that only use JavaScript/TypeScript don't need these. + +### Installing the NativeScript CLI + +If you haven't already, install the NativeScript CLI globally: + +```bash +npm install -g nativescript +``` + +### Verifying the environment + +Run: + +```bash +ns doctor +``` + +On Linux the output includes a Linux section, which checks that the GTK 4 library (`libgtk-4.so.1`) is installed. If it reports no issues, your environment is ready. + +Your machine is also listed as a `Linux` device: + +```bash +ns devices linux +``` + +You're now ready to [add the Linux platform to a project](/guide/linux/#adding-linux-to-a-project). + ## Setting up Linux for iOS :::danger :x: Unsupported diff --git a/content/sidebar.ts b/content/sidebar.ts index 1a61165b..97bebda4 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -244,6 +244,19 @@ export default [ }, ] }, + { + text: 'Developing for Linux', + items: [ + { + text: 'Developing for Linux', + link: '/guide/linux/', + }, + { + text: 'Distributing Linux apps', + link: '/guide/publishing/linux', + }, + ] + }, { text: 'Agentic Coding', items: [ @@ -272,6 +285,10 @@ export default [ text: 'Adding ObjectiveC/Swift Code', link: '/guide/native-code/ios', }, + { + text: 'Adding Linux Code', + link: '/guide/native-code/linux', + }, { text: 'Generating TypeScript types', link: '/guide/native-code/generate-typings', @@ -290,6 +307,10 @@ export default [ text: 'iOS', link: '/guide/extending-classes-and-conforming-to-protocols-ios', }, + { + text: 'Linux', + link: '/guide/extending-classes-and-implementing-interfaces-linux', + }, ], }, { @@ -340,6 +361,10 @@ export default [ text: 'Android Marshalling', link: '/guide/android-marshalling', }, + { + text: 'Linux Marshalling', + link: '/guide/linux-marshalling', + }, ], }, { diff --git a/content/troubleshooting.md b/content/troubleshooting.md index 7da0d794..812d523e 100644 --- a/content/troubleshooting.md +++ b/content/troubleshooting.md @@ -212,3 +212,39 @@ The above can be ignored by adding the following to your `tsconfig.json`: ::: + +## Linux + +::: warning Experimental +The Linux platform is experimental. See [Developing for Linux](/guide/linux/). +::: + +### The CLI says Linux apps can't be built on this OS + +Linux apps can only be built on a Linux machine. On macOS and Windows, `ns run linux` and `ns build linux` fail with `Applications for platform Linux can not be built on this OS`. See [Setting up Linux for Linux desktop](/setup/linux#setting-up-linux-for-linux-desktop). + +### `ns doctor` reports that GTK 4 is missing + +Install the GTK 4 library with your distribution's package manager, for example `sudo apt install libgtk-4-1` or `sudo dnf install gtk4`. See [Installing GTK 4](/setup/linux#installing-gtk-4). + +### Building native code fails because a tool is missing + +Building C or C++ code in `App_Resources/Linux` or a plugin requires a C compiler, `pkg-config` and, for `CMakeLists.txt` projects, `cmake`. On Debian and Ubuntu run `sudo apt install build-essential pkg-config cmake`, and install the development packages of the libraries listed in `native.json` (for example `libgtk-4-dev` for `gtk4`). + +### A C function is `undefined` + +The function's library couldn't be loaded, or the library doesn't export the function. The runtime prints the library names it tried when a library doesn't load. Check that the library is installed, and that the `link` entries of your [module map](/guide/native-code/linux#using-module-maps) name it correctly. + +Function-like C macros (such as `G_OBJECT()` or `G_TYPE_STRING`) are not functions and are never available, see [Linux Marshalling](/guide/linux-marshalling#functions-enums-and-constants). + +### Writing a file fails with EROFS + +In release builds the app's files are packed into a read-only `app.nsbundle`. Write files to `knownFolders.documents()` or `knownFolders.temp()` instead of the app folder, or disable [`linux.bundle`](/configuration/nativescript#linux-bundle). + +### The app shows a generic icon or name in the dock + +The desktop matches the app's windows to its desktop entry by the app id. Run the app at least once with `ns run linux` to install the debug desktop entry, and make sure `Icon` and `StartupWMClass` in `App_Resources/Linux/app.desktop` are `__APP_ID__`. + +### GTK warnings or critical messages in the console + +`Gtk-CRITICAL` and `GLib-GObject-CRITICAL` messages come from GTK, usually when a C function is called with an invalid argument (for example a `NULL` widget, or a widget that was already destroyed). To find where they come from, see [Debugging native code with GDB](/guide/debugging#debugging-native-code-with-gdb). diff --git a/content/ui/action-bar.md b/content/ui/action-bar.md index ef767e5c..1142fa23 100644 --- a/content/ui/action-bar.md +++ b/content/ui/action-bar.md @@ -348,13 +348,16 @@ See [R.drawable](https://developer.android.com/reference/android/R.drawable.html - Android: [android.widget.Toolbar](https://developer.android.com/reference/android/widget/Toolbar.html) - iOS: [UINavigationBar](https://developer.apple.com/documentation/uikit/uinavigationbar?language=objc) +- Linux: a [GtkCenterBox](https://docs.gtk.org/gtk4/class.CenterBox.html) at the top of the page, containing the navigation button, the title and the action items ### ActionItem - Android: [android.widget.Toolbar](https://developer.android.com/reference/android/widget/Toolbar.html) - iOS: [UINavigationItem](https://developer.apple.com/documentation/uikit/uinavigationitem?language=objca) +- Linux: [GtkButton](https://docs.gtk.org/gtk4/class.Button.html) ### NavigationButton - Android: [android.widget.Toolbar](https://developer.android.com/reference/android/widget/Toolbar.html) - iOS: [UINavigationItem](https://developer.apple.com/documentation/uikit/uinavigationitem?language=objca) +- Linux: [GtkButton](https://docs.gtk.org/gtk4/class.Button.html) diff --git a/content/ui/activity-indicator.md b/content/ui/activity-indicator.md index e9e6ad26..63db7e3f 100644 --- a/content/ui/activity-indicator.md +++ b/content/ui/activity-indicator.md @@ -99,3 +99,4 @@ See [EventData](/api/interface/EventData). - Android: [`android.widget.ProgressBar` (indeterminate = true)](https://developer.android.com/reference/android/widget/ProgressBar.html) - iOS: [`UIActivityIndicatorView`](https://developer.apple.com/documentation/uikit/uiactivityindicatorview) +- Linux: [`GtkSpinner`](https://docs.gtk.org/gtk4/class.Spinner.html) diff --git a/content/ui/button.md b/content/ui/button.md index 23bf433c..7a03e95c 100644 --- a/content/ui/button.md +++ b/content/ui/button.md @@ -121,3 +121,4 @@ See [TapGestureEventData](/api/interface/TapGestureEventData). - Android: [`android.widget.Button`](https://developer.android.com/reference/android/widget/Button.html) - iOS: [`UIButton`](https://developer.apple.com/documentation/uikit/uibutton) +- Linux: [`GtkButton`](https://docs.gtk.org/gtk4/class.Button.html) diff --git a/content/ui/date-picker.md b/content/ui/date-picker.md index 052e2c57..57acd5bb 100644 --- a/content/ui/date-picker.md +++ b/content/ui/date-picker.md @@ -201,3 +201,4 @@ See [PropertyChangeData](/api/interface/PropertyChangeData). - Android: [`android.widget.DatePicker`](https://developer.android.com/reference/android/widget/DatePicker.html) - iOS: [`UIDatePicker`](https://developer.apple.com/documentation/uikit/uidatepicker) +- Linux: [`GtkCalendar`](https://docs.gtk.org/gtk4/class.Calendar.html) (with [`GtkSpinButton`](https://docs.gtk.org/gtk4/class.SpinButton.html)s for the time when `showTime` is set) diff --git a/content/ui/dialogs.md b/content/ui/dialogs.md index 8aa0c104..d2eeb855 100644 --- a/content/ui/dialogs.md +++ b/content/ui/dialogs.md @@ -291,3 +291,4 @@ See [login()](/api/#login). - Android: [android.app.AlertDialog.Builder](https://developer.android.com/reference/android/app/AlertDialog.Builder) - iOS: [UIAlertController](https://developer.apple.com/documentation/uikit/uialertcontroller) +- Linux: a modal [`GtkWindow`](https://docs.gtk.org/gtk4/class.Window.html) diff --git a/content/ui/frame.md b/content/ui/frame.md index 6c719b7b..ec2596a8 100644 --- a/content/ui/frame.md +++ b/content/ui/frame.md @@ -371,3 +371,4 @@ If set to `true`, it clears the navigation history backstack. - Android: [`org.nativescript.widgets.ContentLayout`](https://github.com/NativeScript/tns-core-modules-widgets/blob/master/android/widgets/src/main/java/org/nativescript/widgets/ContentLayout.java) - iOS: [`UINavigationController`](https://developer.apple.com/documentation/uikit/uinavigationcontroller) +- Linux: a [`GtkFixed`](https://docs.gtk.org/gtk4/class.Fixed.html) container that hosts the current page diff --git a/content/ui/html-view.md b/content/ui/html-view.md index 77cbcc7e..bd5a4228 100644 --- a/content/ui/html-view.md +++ b/content/ui/html-view.md @@ -88,3 +88,4 @@ For additional inherited properties, refer to the [API Reference](/api/class/Htm - Android: [`android.widget.TextView`](https://developer.android.com/reference/android/widget/TextView.html) - iOS: [`UITextView`](https://developer.apple.com/documentation/uikit/uitextview) +- Linux: [`GtkLabel`](https://docs.gtk.org/gtk4/class.Label.html), showing the HTML converted to [Pango markup](https://docs.gtk.org/Pango/pango_markup.html) (a basic subset of HTML is supported) diff --git a/content/ui/image.md b/content/ui/image.md index 0770d1ba..49bc346a 100644 --- a/content/ui/image.md +++ b/content/ui/image.md @@ -308,3 +308,4 @@ For additional inherited properties, refer to the [API Reference](/api/class/Ima - Android: [`android.widget.ImageView`](https://developer.android.com/reference/android/widget/ImageView) - iOS: [`UIImageView`](https://developer.apple.com/documentation/uikit/uiimageview) +- Linux: [`GtkPicture`](https://docs.gtk.org/gtk4/class.Picture.html) diff --git a/content/ui/label.md b/content/ui/label.md index 5019c7d1..207f328f 100644 --- a/content/ui/label.md +++ b/content/ui/label.md @@ -191,3 +191,4 @@ Emitted when the label text is changed. --> - Android: [`android.widget.TextView`](https://developer.android.com/reference/android/widget/TextView.html) - iOS: [`UILabel`](https://developer.apple.com/documentation/uikit/uilabel) +- Linux: [`GtkLabel`](https://docs.gtk.org/gtk4/class.Label.html) diff --git a/content/ui/list-picker.md b/content/ui/list-picker.md index f8a16dd8..4f8da2b2 100644 --- a/content/ui/list-picker.md +++ b/content/ui/list-picker.md @@ -95,3 +95,4 @@ Emitted when the currently selected item (index) changes. - Android: [`android.widget.NumberPicker`](https://developer.android.com/reference/android/widget/NumberPicker.html) - iOS: [`UIPickerView`](https://developer.apple.com/documentation/uikit/uipickerview) +- Linux: [`GtkDropDown`](https://docs.gtk.org/gtk4/class.DropDown.html) diff --git a/content/ui/list-view.md b/content/ui/list-view.md index a0677dea..ebe5b0cd 100644 --- a/content/ui/list-view.md +++ b/content/ui/list-view.md @@ -384,3 +384,4 @@ See `SearchEventData` in the API reference for the shape of the event. - Android: [`android.widget.ListView`](https://developer.android.com/reference/android/widget/ListView.html) - iOS: [`UITableView`](https://developer.apple.com/documentation/uikit/uitableview) +- Linux: [`GtkListView`](https://docs.gtk.org/gtk4/class.ListView.html) in a [`GtkScrolledWindow`](https://docs.gtk.org/gtk4/class.ScrolledWindow.html) diff --git a/content/ui/page.md b/content/ui/page.md index 02e94a9a..c8bd34b7 100644 --- a/content/ui/page.md +++ b/content/ui/page.md @@ -454,3 +454,4 @@ See also: [enableEdgeToEdge](/core/utils#enableedgetoedge). - Android: [`org.nativescript.widgets.GridLayout`](https://github.com/NativeScript/NativeScript/blob/master/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/GridLayout.java) - iOS: [`UIViewController`](https://developer.apple.com/documentation/uikit/uiviewcontroller) +- Linux: a [`GtkFixed`](https://docs.gtk.org/gtk4/class.Fixed.html) container diff --git a/content/ui/progress.md b/content/ui/progress.md index fabf04a0..99a00130 100644 --- a/content/ui/progress.md +++ b/content/ui/progress.md @@ -96,3 +96,4 @@ For additional inherited properties, refer to the [API Reference](/api/class/Pro - Android: [`android.widget.ProgressBar` (indeterminate = false)](https://developer.android.com/reference/android/widget/ProgressBar.html) - iOS: [`UIProgressView`](https://developer.apple.com/documentation/uikit/uiprogressview) +- Linux: [`GtkProgressBar`](https://docs.gtk.org/gtk4/class.ProgressBar.html) diff --git a/content/ui/scroll-view.md b/content/ui/scroll-view.md index 7a89f824..68afdf3a 100644 --- a/content/ui/scroll-view.md +++ b/content/ui/scroll-view.md @@ -160,3 +160,4 @@ See [ScrollEventData](/api/interface/ScrollEventData). - Android: [`android.view`](https://developer.android.com/reference/android/view/View.html) - iOS: [`UIScrollView`](https://developer.apple.com/documentation/uikit/uiscrollview) +- Linux: [`GtkScrolledWindow`](https://docs.gtk.org/gtk4/class.ScrolledWindow.html) diff --git a/content/ui/search-bar.md b/content/ui/search-bar.md index bd2f880b..0cfc0a6d 100644 --- a/content/ui/search-bar.md +++ b/content/ui/search-bar.md @@ -139,3 +139,4 @@ Emitted when the search input is cleared through the **✗** button in the i - Android: [`android.widget.SearchView`](https://developer.android.com/reference/android/widget/SearchView.html) - iOS: [`UISearchBar`](https://developer.apple.com/documentation/uikit/uisearchbar) +- Linux: [`GtkSearchEntry`](https://docs.gtk.org/gtk4/class.SearchEntry.html) diff --git a/content/ui/segmented-bar.md b/content/ui/segmented-bar.md index 087ad071..62b57939 100644 --- a/content/ui/segmented-bar.md +++ b/content/ui/segmented-bar.md @@ -121,3 +121,4 @@ Emitted when an item in the SegmentedBar is tapped. - Android: [`android.widget.TabHost`](https://developer.android.com/reference/android/widget/TabHost.html) - iOS: [`UISegmentedControl`](https://developer.apple.com/documentation/uikit/uisegmentedcontrol) +- Linux: a [`GtkBox`](https://docs.gtk.org/gtk4/class.Box.html) of [`GtkToggleButton`](https://docs.gtk.org/gtk4/class.ToggleButton.html)s diff --git a/content/ui/slider.md b/content/ui/slider.md index 17613532..a4bb5b06 100644 --- a/content/ui/slider.md +++ b/content/ui/slider.md @@ -99,3 +99,4 @@ See [PropertyChangeData](/api/interface/PropertyChangeData). - Android: [`android.widget.SeekBar`](https://developer.android.com/reference/android/widget/SeekBar.html) - iOS: [`UISlider`](https://developer.apple.com/documentation/uikit/uislider) +- Linux: [`GtkScale`](https://docs.gtk.org/gtk4/class.Scale.html) diff --git a/content/ui/split-view.md b/content/ui/split-view.md index aafe20fd..58aed64f 100644 --- a/content/ui/split-view.md +++ b/content/ui/split-view.md @@ -220,3 +220,4 @@ Emitted whenever the inspector column changes visibility. Payload contains a `da ## Native component - iOS: [`UISplitViewController`](https://developer.apple.com/documentation/uikit/uisplitviewcontroller) +- Linux: a [`GtkFixed`](https://docs.gtk.org/gtk4/class.Fixed.html) container, the columns are laid out by NativeScript diff --git a/content/ui/switch.md b/content/ui/switch.md index 22d0d188..4e46f2a1 100644 --- a/content/ui/switch.md +++ b/content/ui/switch.md @@ -103,3 +103,4 @@ See [PropertyChangeData](/api/interface/PropertyChangeData). - Android: [`android.widget.Switch`](https://developer.android.com/reference/android/widget/Switch.html) - iOS: [`UISwitch`](https://developer.apple.com/documentation/uikit/uiswitch) +- Linux: [`GtkSwitch`](https://docs.gtk.org/gtk4/class.Switch.html) diff --git a/content/ui/tab-view.md b/content/ui/tab-view.md index b5839d75..c1864471 100644 --- a/content/ui/tab-view.md +++ b/content/ui/tab-view.md @@ -295,3 +295,4 @@ tabView.iosBottomAccessory = accessory - Android: [`androidx.viewpager.widget.ViewPager`](https://developer.android.com/reference/androidx/viewpager/widget/ViewPager) - iOS: [`UITabBarController`](https://developer.apple.com/documentation/uikit/uitabbarcontroller) +- Linux: [`GtkNotebook`](https://docs.gtk.org/gtk4/class.Notebook.html) diff --git a/content/ui/text-field.md b/content/ui/text-field.md index bd3f6e07..1de92053 100644 --- a/content/ui/text-field.md +++ b/content/ui/text-field.md @@ -244,3 +244,4 @@ Emitted when the TextField loses focus. - Android: [`android.widget.EditText`](https://developer.android.com/reference/android/widget/EditText.html) - iOS: [`UITextField`](https://developer.apple.com/documentation/uikit/uitextfield) +- Linux: [`GtkEntry`](https://docs.gtk.org/gtk4/class.Entry.html) diff --git a/content/ui/text-view.md b/content/ui/text-view.md index 9ae677be..0f1684e8 100644 --- a/content/ui/text-view.md +++ b/content/ui/text-view.md @@ -264,3 +264,4 @@ Emitted when the TextView loses focus. - Android: [`android.widget.EditText`](https://developer.android.com/reference/android/widget/EditText.html) - iOS: [`UITextView`](https://developer.apple.com/documentation/uikit/uitextview) +- Linux: [`GtkTextView`](https://docs.gtk.org/gtk4/class.TextView.html) in a [`GtkScrolledWindow`](https://docs.gtk.org/gtk4/class.ScrolledWindow.html) diff --git a/content/ui/time-picker.md b/content/ui/time-picker.md index c85f0b27..63cb1a3a 100644 --- a/content/ui/time-picker.md +++ b/content/ui/time-picker.md @@ -143,3 +143,4 @@ Emitted when the selected time changes. - Android: [`android.widget.TimePicker`](https://developer.android.com/reference/android/widget/TimePicker) - iOS: [`UIDatePicker`](https://developer.apple.com/documentation/uikit/uidatepicker) +- Linux: a [`GtkBox`](https://docs.gtk.org/gtk4/class.Box.html) of [`GtkSpinButton`](https://docs.gtk.org/gtk4/class.SpinButton.html)s diff --git a/content/ui/web-view.md b/content/ui/web-view.md index c906ca2f..25c77587 100644 --- a/content/ui/web-view.md +++ b/content/ui/web-view.md @@ -180,3 +180,4 @@ See [LoadEventData](/api/interface/LoadEventData). - Android: [`android.webkit.WebView`](https://developer.android.com/reference/android/webkit/WebView) - iOS: [`WKWebView`](https://developer.apple.com/documentation/webkit/wkwebview) +- Linux: not supported yet, shows the URL and a button that opens it in the default browser