با زبان برنامهنویسی سلام میتوان از سه راه برنامهی دسکتاپ (رومیزی) برای ویندوز، مک و لینوکس ساخت. بستهی رابط گرافیکی.نمایش وب (gui.webview) برنامهای دسکتاپ میسازد که رابطش HTML، CSS و جاوااسکریپت است و درون نمایشگر وبی (WebView) اجرا میشود که سیستمعامل از پیش دارد؛ همان مدل Tauri، با این تفاوت که منطق برنامه به سلام نوشته میشود و هیچ مرورگری همراه برنامه بستهبندی نمیشود. محیط اجرای رابط گرافیکی.رابط کاربری (gui.ui) راه دیگری میرود و یک صفحهی سلام را خودش، بدون هیچ مرورگری، در یک پنجرهی بومی میکشد. برای بازی و برنامههای تصویری هم gui.raylib کتابخانهی raylib را در دسترس میگذارد. یک فرمان، salam bundle، از پروژه برای هر سه سیستمعامل نصبکننده میسازد.
این مقاله هر گزینه را با کد واقعی نشان میدهد، توضیح میدهد بستهبندی و ساخت برای سیستمعاملهای دیگر چطور کار میکند، و دقیق میگوید چه چیزی کامل است و چه چیزی نه.
سه راه ساخت رابط گرافیکی با زبان سلام
| روش | بسته | چه چیزی پیکسلها را میکشد | مناسب برای |
|---|---|---|---|
| رابط وب | gui.webview | WebView2، WKWebView یا WebKitGTK | ابزارها و برنامههایی با رابط غنی که ساختنش را با HTML بلدید |
| رابط بومی از روی صفحه | gui.ui | خود سلام (چیدمان، متن، نقاشی) | صفحهای که به زبان صفحهی سلام نوشته شده و بدون موتور مرورگر نمایش داده میشود |
| بازی و گرافیک | gui.raylib | raylib روی OpenGL | بازی، شبیهسازی، اسباببازیهای تصویری، آموزش |
راه اول راه پخته است: مدل امنیتی، خدمات سیستمی، نصبکننده و یک گردشکار CI روی ماشینهای واقعی ویندوز، مک و لینوکس دارد. راه دوم تازهتر است و هنوز در حال رشد. راه سوم پیوندی به یک کتابخانهی شناختهشدهی C است، پس نقاط قوت raylib و نیازمندیهایش را با هم به ارث میبرد.
برنامهی دسکتاپ با WebView، به سبک Tauri
Electron یک نسخهی کامل از Chromium را همراه هر برنامه میفرستد. Tauri نشان داد که میتوان به جای آن از نمایشگر وبی استفاده کرد که سیستمعامل از قبل دارد، و این حجم دانلود را کوچک نگه میدارد. gui.webview در سلام از مدل دوم پیروی میکند، بدون Rust و بدون زبان دومی برای بخش پشتی (backend):
| سیستمعامل | موتور | نیاز در زمان اجرا |
|---|---|---|
| لینوکس | WebKitGTK | libgtk-3-0 و libwebkit2gtk-4.1-0 |
| ویندوز | WebView2 | محیط اجرای WebView2 که در ویندوزهای امروزی هست |
| مک | WKWebView | هیچ |
salam new notes --desktop یک پروژهی کامل مینویسد: main.salam، یک salam.toml، یک .gitignore و پوشهی www با index.html، app.js و style.css. اگر --lang=fa را اضافه کنید، پروژه به فارسی نوشته میشود. نقطهی ورود ساختهشده کوتاه است؛ این همان برنامه به فارسی است:
واردسازی رابط گرافیکی.نمایش وب
تابع ریشه:
نمایش وب.آغاز(نمایش وب.پیکربندی { شناسه = "com.example.notes"، نام = "یادداشتها"، پوشه دارایی ها = "www" })
نمایش وب.فرمان("greet"، (ف: نمایش وب.فراخوان):
ف.پاسخ متنی("سلام " + ف.متن("name"))
پایان)
پنجره := نمایش وب.باز کردن(نمایش وب.گزینه های پنجره { عنوان = "یادداشتها"، پهنا = ۹۰۰، بلندی = ۶۰۰ })
اگر پنجره.بسته شده است():
سرچاپ "پنجره باز نشد"
برگشت ۱
پایان
نمایش وب.اجرا()
پایانآغاز برنامه را توصیف میکند، فرمان تابعی را ثبت میکند که صفحه میتواند صدا بزند، باز کردن پنجره میسازد و اجرا حلقهی رویداد را تا وقتی کاربر برنامه را ببندد میچرخاند. صفحه این فرمان را مثل هر تابع async جاوااسکریپت صدا میزند:
const text = await salam.invoke('greet', { name: 'Sara' });در زمان توسعه، پوشه دارایی ها (assets_dir) خود پوشه را مستقیم سرو میکند، پس با یک بارگذاری دوباره تغییراتتان را میبینید. در ساخت نهایی همین پروندهها درون پروندهی اجرایی بستهبندی و از app://localhost (روی ویندوز https://app.localhost) سرو میشوند. صفحه میتواند از یک سایت راه دور هم بیاید (نشانی یا url در گزینههای پنجره) یا از یک رشتهی HTML (html).
گفتوگوی صفحه و سلام
سه فراخوانی کل این پل را پوشش میدهند:
| از سمت صفحه | در سلام |
|---|---|
await salam.invoke(name, args) | webview.Command روی نخ رابط کاربری اجرا میشود و webview.CommandAsync روی یک نخ کارگر |
salam.emit(name, data) | webview.On(name, handler) |
salam.listen(name, fn) | webview.Emit(name, json)، یا win.Emit برای یک پنجره |
در این جدول و بخشهای بعدی نامهای انگلیسی بسته آمدهاند، چون صفحهی مرجع با همین نامها مرتب شده است؛ هر کدام در سلام فارسی نام فارسی خودش را دارد، مثل نمایش وب.فرمان برای webview.Command. آرگومانها و نتیجهها به شکل JSON جابهجا میشوند. هر فرمان دقیقاً یک بار پاسخ میدهد، با یکی از متدهای Resolve (مثل پاسخ متنی در مثال بالا) یا با Reject که Promise صفحه را با خطا روبهرو میکند. کار کند جایش در CommandAsync است، چون Command ساده روی نخ رابط کاربری اجرا میشود و پنجره را از کار میاندازد. فراخوانیهای پنجره هم باید روی نخ رابط کاربری انجام شوند، پس نخ کارگر آنها را با webview.Dispatch تحویل میدهد. اگر درس کار همزمان را خوانده باشید، این همان قاعدهی همیشگی همهی جعبهابزارهای رابط گرافیکی است که اینجا صریح نوشته شده است.
مدل امنیتی
برنامهی دسکتاپی که محتوای وب اجرا میکند به مرز روشنی میان صفحه و ماشین نیاز دارد. gui.webview این مرز را اینطور میکشد:
- صفحههای محلی میتوانند همهی فرمانها را اجرا کنند. مبداهای راه دور هیچ فرمانی را اجرا نمیکنند تا وقتی آنها را با
webview.Allow(origin, commands)نام ببرید. - پل فقط در قاب بالایی یک مبدأ مجاز نصب میشود. هر پیام رازی را با خود دارد که برای همین اجرا ساخته شده و همراه با مبدأ صفحه بررسی میشود، پس یک قاب جاسازیشده یا سایت دیگری نمیتواند فرمانی را صدا بزند.
- پیوند به هر مبدأ دیگری در مرورگر سیستم باز میشود و مسیرهایی که بخواهند از پوشهی داراییها بیرون بروند رد میشوند.
- داراییهای بستهبندیشده فشرده و درهم میشوند تا در خروجی
stringsدیده نشوند، اما این فقط پنهانشان میکند و از آنها محافظت نمیکند. رازها جایشان روی سرور است.
خدمات سیستمی
یک فراخوانی سلام روی هر سیستمعامل به امکان بومی همان سیستم نگاشته میشود: پنجرههای انتخاب پرونده (PickFolder، PickFile، SaveFile)، پیامها (Message، Confirm)، اعلانها (Notify)، جاکلیدی (SecretSet، SecretGet، SecretDelete بر پایهی libsecret، Credential Manager یا Keychain)، نوار منو (SetMenu)، نماد سینی سیستم (SetTray)، قفل تکنمونه (SingleInstance) و OpenURL. اگر ماشین لینوکسی سرویس راز نداشته باشد، راز در پروندهای با مجوز 0600 در پوشهی دادهی برنامه نگه داشته میشود. صفحهی مرجع gui.webview برای هر کدام راهنمایی دارد.
ساخت نصبکننده با salam bundle
برنامه را در جدول [app] پروندهی salam.toml توصیف کنید:
[app]
id = "com.example.notes"
name = "Notes"
version = "1.0.0"
main = "main.salam"
assets = "www"
icon = "icon.png"سپس یک فرمان بستهها را میسازد:
salam bundle
salam bundle --target=x86_64-w64-windows-gnu
salam bundle --target=arm64-apple-macos --format=app,tar| سیستمعامل | قالبهای پیشفرض | بهعلاوه، وقتی ابزارش در PATH باشد |
|---|---|---|
| لینوکس | tar، deb | rpm (rpmbuild)، appimage (appimagetool) |
| ویندوز | zip | nsis (makensis) |
| مک | app، tar | dmg (hdiutil) |
ساخت برای سیستمعاملهای دیگر به چیز اضافهای نیاز ندارد، چون کامپایلر خودش برنامههای ویندوز و مک را پیوند (link) میدهد (مقالهی کامپایل متقابل در سلام را ببینید). روی لینوکس و ویندوز داراییها به انتهای پروندهی اجرایی افزوده میشوند؛ روی مک به Contents/Resources/app.pack میروند تا امضای برنامه معتبر بماند. نماد PNG هم خودکار به .ico و .icns تبدیل میشود.
هیچ چیزی برای شما امضا نمیشود. ساختهای مک امضای موقت (ad-hoc) کامپایلر را دارند که پس از برداشتن پرچم قرنطینه برای اجرا کافی است، اما توزیع واقعی، مثل هر زنجیرهابزار دیگری، به امضا و تأیید رسمی (notarization) نیاز دارد.
برای برنامههای ویندوزی که نباید پنجرهی کنسول باز کنند، salam build گزینههای --subsystem=windows، --icon=FILE.ico و --version-info=KEY=VALUE را هم میپذیرد که به پشتیبان بومی یا پیونددهندهی درونی نیاز دارند.
موتور Chromium و محدودیتهای فعلی آن
بعضی برنامهها روی همهی سیستمها به یک موتور یکسان نیاز دارند. ساختن با -DSALAM_WEBVIEW_CEF و بستهبندی با --engine=chromium --runtime=cef-dist موتور Chromium را از راه Chromium Embedded Framework جایگزین میکند. این موتور هنوز کامل نیست: پنجرهها باز و صفحهها بارگذاری میشوند، اما کانال پیام، طرح نشانی سفارشی و هیچکدام از خدمات سیستمی وجود ندارد؛ پس webview.SupportsCommands() مقدار false برمیگرداند و صفحه نمیتواند فرمانهای شما را صدا بزند. برای برنامهای که امروز میسازید، نمایشگر وب سیستمعامل موتور درست است.
رابط کاربری بومی که خود سلام میکشد
نمایشگر وب هنوز یعنی وابستگی به یک موتور مرورگر و پذیرفتن زمان شروع و مصرف حافظهاش. راه دوم سلام مرورگر را کاملاً کنار میگذارد. برنامهای که به زبان صفحه نوشته شده میتواند با --ui=native کامپایل شود و محیط اجرای gui.ui آن را در پنجرهای بومی نشان میدهد که سلام پیکسلبهپیکسل میکشد؛ همان کاری که Slint و Flutter میکنند، یعنی ویجتها را خودشان میکشند و از ویجتهای سیستمعامل استفاده نمیکنند.
این محیط اجرا پشتهای از بستههای سلام است: gui.window (X11، Win32، Cocoa و یک پشتیبان بیسر)، gui.paint (مسیرهای ضدپله، حاشیه، گرادیان، سایه)، gui.typeset (قلمهای TrueType، شکلدهی حروف، متن دوسویه و شکستن سطر، پس فارسی از همان ابتدا درست کار میکند)، gui.cssbox (چیدمان block، flex، grid و جدول)، gui.css (آبشار سبکها) و gui.reactive. این یک شمارندهی کامل است:
ناپایا شمار := ۰
صفحه:
عنوان = "شمارنده"
جعبه:
سرتیتر: محتوا = "شمار: " + شمار پایان
دکمه: شناسه = "افزودن" محتوا = "+۱" کلیک = (): شمار++ پایان پایان
پایان
پایانهمین پرونده به هر دو شکل ساخته میشود:
salam page build counter.salam # counter.html و counter.js برای وب
salam run counter.salam --ui=native # همان صفحه در یک پنجرهی بومی
salam build counter.salam --ui=native # یک پروندهی اجرایی بومیدر ساخت بومی، کامپایلر همهی کار سبکها را در زمان کامپایل انجام میدهد: هر اعلان CSS هنگام کامپایل تجزیه، اعتبارسنجی و باز میشود و صفحه به تابعی از فراخوانیهای سازنده تبدیل میشود. هنگام اجرای برنامه هیچ متن HTML یا CSS ای تجزیه نمیشود. پس از هر گردانندهی رویداد، فقط بخشهایی از نما که وضعیت تغییرکرده را خواندهاند دوباره اجرا میشوند، و حالت hover، فوکوس، جای اسکرول و متن تایپشده حفظ میشود. مقالهی مؤلفهها، وضعیت و رویدادها این مدل واکنشی را شرح میدهد.
محیط اجرای بومی برای آزمون میتواند بیسر (headless) کار کند. ساختن شمارندهی فارسی بالا روی لینوکس x86-64 پروندهای اجرایی به حجم ۲٫۵ مگابایت داد؛ وقتی با SALAM_WINDOW_BACKEND=headless و اسکریپتی اجرا شد که سه بار روی دکمه کلیک میکند و سپس درخت عنصرها را چاپ میکند، این را گزارش کرد:
body 8 21 1008 99
div 8 21 1008 99
h1 "شمار: 3" 8 21 1008 50
button#افزودن "+۱" focused 985 93 31 27به جای دکمه دقت کنید (985 از چپ): صفحهای که به فارسی نوشته شده خودکار dir="rtl" میگیرد، پس دکمه در سمت راست پنجره مینشیند، بیآنکه یک خط CSS نوشته باشید.
میتوانید gui.ui را از یک برنامهی معمولی هم به کار بیندازید. ثبت رسیدگی یک گردانندهی نامدار ثبت میکند، نما تابعی است که سند را برمیگرداند، و گزینههای بی سر و گام های آزمایشی آن را بدون نمایشگر اجرا میکنند؛ این مثال روی ماشین ساخت وبسایت هم به همین شکل اجرا میشود:
واردسازی رابط گرافیکی.رابط کاربری
ناپایا انجامشده := ۰
تابع نما(): رشته:
برگشت `<!doctype html><html><head><title>کارها</title></head><body><h1>انجامشده: ` + انجامشده + `</h1><button id="finish" onclick="finish()">یکی را تمام کن</button></body></html>`
پایان
تابع ریشه:
رابط کاربری.ثبت رسیدگی("finish"، (): انجامشده++ پایان)
گزینهها := رابط کاربری.گزینهها { عنوان = "کارها"، پهنا = ۳۲۰.۰، بلندی = ۲۰۰.۰، بی سر = درست، گام های آزمایشی = "click #finish\nclick #finish\nquit" }
رابط کاربری.اجرا با((): برگشت نما() پایان، گزینهها)
سرچاپ "کارهای تمامشده:"، انجامشده
پایانآنچه هدف بومی نمیتواند نشان دهد
بعضی امکانات صفحه فقط در مرورگر معنا دارند. با --ui=native کامپایلر عنصرهایی مثل script، style خام، iframe و svg درونخطی را با خطای E165 رد میکند و فرادادههای وب مثل meta را با هشدار W166 نادیده میگیرد. یک صفحه میتواند بخشهای مخصوص وب را با اگر سلام رابط وب: (در برنامهی انگلیسی if SALAM_UI_WEB:) کنار بخشهای قابلحمل نگه دارد. سند طراحی آنچه هنوز در برنامه است را فهرست میکند: دسترسپذیری از روی نقشهای aria، نقاشی با GPU، Wayland (پشتیبان لینوکس امروز X11 است)، زیرمجموعهای از SVG، و اندروید و iOS. برای برنامهای که همین امروز باید برای صفحهخوانها دسترسپذیر باشد، راه نمایشگر وب انتخاب امنتری است.
بازی و گرافیک با raylib
gui.raylib پیوندی به raylib نسخهی ۵٫۵ است، کتابخانهای کوچک به زبان C برای بازیهای دوبعدی و سهبعدی. نامهای انگلیسی تابعها از برگهی خلاصهی (cheat sheet) raylib پیروی میکنند، پس هر آموزش raylib تقریباً خطبهخط ترجمه میشود؛ در سلام فارسی هم هر تابع نام فارسی خودش را دارد:
واردسازی رابط گرافیکی.ریلیب
تابع ریشه:
ریلیب.ایجاد پنجره(۸۰۰، ۴۵۰، "توپ جهنده")
ریلیب.تنظیم نرخ فریم هدف(۶۰)
ناپایا ایکس := ۴۰۰.۰
ناپایا سرعت := ۲۴۰.۰
تا وارونه ریلیب.پنجره باید بسته شود():
دت := ریلیب.زمان فریم() برگردان اعشار۶۴
ایکس += سرعت * دت
اگر ایکس < ۲۰.۰ یا ایکس > ۷۸۰.۰:
سرعت = ۰.۰ - سرعت
پایان
ریلیب.شروع رسم()
ریلیب.پاک کردن پسزمینه(ریلیب.سفید ری())
ریلیب.رسم دایره(ایکس برگردان صحیح، ۲۲۵، ۲۰.۰، ریلیب.عنابی())
ریلیب.پایان رسم()
پایان
ریلیب.بستن پنجره()
پایانتا در سلام حلقه را تا وقتی شرطش درست است ادامه میدهد، پس این حلقه تا زمانی میچرخد که پنجره نباید بسته شود. برنامهها با libraylib اشتراکی پیوند میخورند که جداگانه نصبش میکنید. raylib ساختارهایی مثل Color و Vector2 را بهصورت مقدار جابهجا میکند؛ سلام این را روی x86-64 و AArch64 با پشتیبانهای پیشفرض مدیریت میکند، اما پشتیبان بومی x86-64 چنین فراخوانیهایی را با پیامی روشن رد میکند. این یک ارتباط با C معمولی است و همین روش برای کتابخانههای گرافیکی دیگر C هم کار میکند.
چگونه آزمایش میشود
کد دسکتاپ معروف است که روی سیستمی که امتحانش نکردهاید بهراحتی میشکند. گردشکار دسکتاپِ مخزن یک آزمون دود (smoke test) را از راه یک پنجرهی واقعی اجرا میکند، یک پروژهی نمونه را بستهبندی و با --self-test روی ماشینهای واقعی لینوکس، ویندوز و مک اجرا میکند، و همهی قالبهای بسته را میسازد، از جمله اجرای salam bundle روی خود ویندوز و مک. منطقهای بدون پنجره در مجموعهی آزمون معمولی هستند و پشتیبان ویندوز زیر Wine هم با یک WebView2 ساختگی آزمایش میشود.
پروژه از این بسته برای نرمافزار خودش هم استفاده میکند. برنامهی دسکتاپ دستیار زبان سلام نسخهای دارد که سراسر به سلام و روی gui.webview نوشته شده و همان گردشکار آزمایشش میکند؛ اما نسخهی منتشرشده هنوز همان ساخت اصلی Tauri است، پس نسخهی سلامی را نمونهای بزرگ و کارشده بدانید، نه محصولی که منتشر میشود.
کدام روش را انتخاب کنیم؟
- از
gui.webviewاستفاده کنید برای بیشتر برنامههای دسکتاپ امروز. یک پشتهی رابط آشنا، پنجرههای سیستمی، جاکلیدی، نماد سینی سیستم، نصبکننده برای سه سیستمعامل و مدل امنیتی سنجیدهای میگیرید. - از
--ui=nativeاستفاده کنید وقتی یک منبع صفحه برای وب و دسکتاپ میخواهید، اصلاً موتور مرورگر نمیخواهید، و با محیط اجرایی که هنوز امکانات تازه میگیرد کنار میآیید. این گزینه برای رابطهای فارسی هم جذاب است، چون شکلدهی حروف و متن دوسویه درونش ساخته شده است. - از
gui.raylibاستفاده کنید برای بازی و هر چیزی که هر فریم دوباره کشیده میشود.
هر کدام را انتخاب کنید، بقیهی برنامه سلام معمولی است، پس کلاینتهای HTTP، پایگاههای داده و کار با پرونده در کتابخانهی استاندارد از درون فرمانها و گردانندههایتان در دسترساند. مقالهی توسعهی وب با سلام زبان صفحه را توضیح میدهد که هم هدف وب و هم هدف بومی از آن شروع میکنند.
پرسشهای پرتکرار دربارهی برنامهی دسکتاپ با زبان سلام
آیا زبان سلام میتواند جای Electron یا Tauri را بگیرد؟
برای بسیاری از برنامهها، بله: gui.webview مثل Tauri از نمایشگر وب سیستم استفاده میکند، پس مرورگری همراه برنامه نیست، و بخش پشتی به جای Rust یا Node.js به سلام نوشته میشود. این بسته از هر دو پروژه بسیار جوانتر است و جامعهی کاربری بسیار کوچکتری دارد، پس انتظار داشته باشید پاسخها را در صفحهی مرجع بسته پیدا کنید، نه در جستوجوی اینترنت.
آیا برنامهی دسکتاپ سلام به نصب محیط اجرا نیاز دارد؟
روی مک، نه. روی ویندوز به محیط اجرای WebView2 نیاز دارد که ویندوزهای امروزی دارند. روی لینوکس به GTK 3 و WebKitGTK 4.1 نیاز دارد که بیشتر توزیعهای دسکتاپ فراهم میکنند. برنامهی بومی --ui=native به هیچکدام نیاز ندارد، چون سلام پنجره را خودش میکشد.
آیا میتوان روی لینوکس نصبکنندهی ویندوز ساخت؟
بله. salam bundle --target=x86_64-w64-windows-gnu برنامه را برای ویندوز میسازد و یک zip، و اگر makensis در PATH باشد یک نصبکنندهی NSIS، بستهبندی میکند. امضا کردن همچنان با خود شماست.
آیا رابط بومی سلام برای محیط تولید آماده است؟
کار میکند و در مخزن بهصورت بیسر آزمایش میشود، اما تازهترین راه از این سه است و نقشهی راهش هنوز دسترسپذیری، Wayland و نقاشی با GPU را در فهرست دارد. اول آن را برای ابزارهای داخلی و نمونههای اولیه امتحان کنید.