mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
Merge pull request #443 from mitchellh/hide-decorations-macos
macos: allow to hide decorations
This commit is contained in:
@ -19,7 +19,7 @@ class PrimaryWindow: NSWindow {
|
|||||||
static func create(ghostty: Ghostty.AppState, appDelegate: AppDelegate, baseConfig: ghostty_surface_config_s? = nil) -> PrimaryWindow {
|
static func create(ghostty: Ghostty.AppState, appDelegate: AppDelegate, baseConfig: ghostty_surface_config_s? = nil) -> PrimaryWindow {
|
||||||
let window = PrimaryWindow(
|
let window = PrimaryWindow(
|
||||||
contentRect: NSRect(x: 0, y: 0, width: 800, height: 600),
|
contentRect: NSRect(x: 0, y: 0, width: 800, height: 600),
|
||||||
styleMask: [.titled, .closable, .miniaturizable, .resizable],
|
styleMask: getStyleMask(renderDecoration: ghostty.windowDecorations),
|
||||||
backing: .buffered,
|
backing: .buffered,
|
||||||
defer: false)
|
defer: false)
|
||||||
window.center()
|
window.center()
|
||||||
@ -45,6 +45,15 @@ class PrimaryWindow: NSWindow {
|
|||||||
return window
|
return window
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func getStyleMask(renderDecoration: Bool) -> NSWindow.StyleMask {
|
||||||
|
var mask: NSWindow.StyleMask = [.resizable, .closable, .miniaturizable]
|
||||||
|
if renderDecoration {
|
||||||
|
mask.insert(.titled)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mask
|
||||||
|
}
|
||||||
|
|
||||||
override var canBecomeKey: Bool {
|
override var canBecomeKey: Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -10,21 +10,21 @@ extension Ghostty {
|
|||||||
enum AppReadiness {
|
enum AppReadiness {
|
||||||
case loading, error, ready
|
case loading, error, ready
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Info {
|
struct Info {
|
||||||
var mode: ghostty_build_mode_e
|
var mode: ghostty_build_mode_e
|
||||||
var version: String
|
var version: String
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The AppState is the global state that is associated with the Swift app. This handles initially
|
/// The AppState is the global state that is associated with the Swift app. This handles initially
|
||||||
/// initializing Ghostty, loading the configuration, etc.
|
/// initializing Ghostty, loading the configuration, etc.
|
||||||
class AppState: ObservableObject {
|
class AppState: ObservableObject {
|
||||||
/// The readiness value of the state.
|
/// The readiness value of the state.
|
||||||
@Published var readiness: AppReadiness = .loading
|
@Published var readiness: AppReadiness = .loading
|
||||||
|
|
||||||
/// Optional delegate
|
/// Optional delegate
|
||||||
weak var delegate: GhosttyAppStateDelegate?
|
weak var delegate: GhosttyAppStateDelegate?
|
||||||
|
|
||||||
/// The ghostty global configuration. This should only be changed when it is definitely
|
/// The ghostty global configuration. This should only be changed when it is definitely
|
||||||
/// safe to change. It is definite safe to change only when the embedded app runtime
|
/// safe to change. It is definite safe to change only when the embedded app runtime
|
||||||
/// in Ghostty says so (usually, only in a reload configuration callback).
|
/// in Ghostty says so (usually, only in a reload configuration callback).
|
||||||
@ -35,7 +35,7 @@ extension Ghostty {
|
|||||||
ghostty_config_free(old)
|
ghostty_config_free(old)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The ghostty app instance. We only have one of these for the entire app, although I guess
|
/// The ghostty app instance. We only have one of these for the entire app, although I guess
|
||||||
/// in theory you can have multiple... I don't know why you would...
|
/// in theory you can have multiple... I don't know why you would...
|
||||||
@Published var app: ghostty_app_t? = nil {
|
@Published var app: ghostty_app_t? = nil {
|
||||||
@ -44,13 +44,13 @@ extension Ghostty {
|
|||||||
ghostty_app_free(old)
|
ghostty_app_free(old)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// True if we need to confirm before quitting.
|
/// True if we need to confirm before quitting.
|
||||||
var needsConfirmQuit: Bool {
|
var needsConfirmQuit: Bool {
|
||||||
guard let app = app else { return false }
|
guard let app = app else { return false }
|
||||||
return ghostty_app_needs_confirm_quit(app)
|
return ghostty_app_needs_confirm_quit(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build information
|
/// Build information
|
||||||
var info: Info {
|
var info: Info {
|
||||||
let raw = ghostty_info()
|
let raw = ghostty_info()
|
||||||
@ -59,10 +59,19 @@ extension Ghostty {
|
|||||||
length: Int(raw.version_len),
|
length: Int(raw.version_len),
|
||||||
encoding: NSUTF8StringEncoding
|
encoding: NSUTF8StringEncoding
|
||||||
) ?? "unknown"
|
) ?? "unknown"
|
||||||
|
|
||||||
return Info(mode: raw.build_mode, version: String(version))
|
return Info(mode: raw.build_mode, version: String(version))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True if we want to render window decorations
|
||||||
|
var windowDecorations: Bool {
|
||||||
|
guard let config = self.config else { return true }
|
||||||
|
var v = false;
|
||||||
|
let key = "window-decoration"
|
||||||
|
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
/// Cached clipboard string for `read_clipboard` callback.
|
/// Cached clipboard string for `read_clipboard` callback.
|
||||||
private var cached_clipboard_string: String? = nil
|
private var cached_clipboard_string: String? = nil
|
||||||
|
|
||||||
@ -73,14 +82,14 @@ extension Ghostty {
|
|||||||
readiness = .error
|
readiness = .error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the global configuration.
|
// Initialize the global configuration.
|
||||||
guard let cfg = Self.loadConfig() else {
|
guard let cfg = Self.loadConfig() else {
|
||||||
readiness = .error
|
readiness = .error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.config = cfg;
|
self.config = cfg;
|
||||||
|
|
||||||
// Create our "runtime" config. The "runtime" is the configuration that ghostty
|
// Create our "runtime" config. The "runtime" is the configuration that ghostty
|
||||||
// uses to interface with the application runtime environment.
|
// uses to interface with the application runtime environment.
|
||||||
var runtime_cfg = ghostty_runtime_config_s(
|
var runtime_cfg = ghostty_runtime_config_s(
|
||||||
@ -110,7 +119,7 @@ extension Ghostty {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
// Subscribe to notifications for keyboard layout change so that we can update Ghostty.
|
// Subscribe to notifications for keyboard layout change so that we can update Ghostty.
|
||||||
NotificationCenter.default.addObserver(
|
NotificationCenter.default.addObserver(
|
||||||
self,
|
self,
|
||||||
@ -120,19 +129,19 @@ extension Ghostty {
|
|||||||
|
|
||||||
self.readiness = .ready
|
self.readiness = .ready
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
// This will force the didSet callbacks to run which free.
|
// This will force the didSet callbacks to run which free.
|
||||||
self.app = nil
|
self.app = nil
|
||||||
self.config = nil
|
self.config = nil
|
||||||
|
|
||||||
// Remove our observer
|
// Remove our observer
|
||||||
NotificationCenter.default.removeObserver(
|
NotificationCenter.default.removeObserver(
|
||||||
self,
|
self,
|
||||||
name: NSTextInputContext.keyboardSelectionDidChangeNotification,
|
name: NSTextInputContext.keyboardSelectionDidChangeNotification,
|
||||||
object: nil)
|
object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes a new configuration and loads all the values.
|
/// Initializes a new configuration and loads all the values.
|
||||||
static func loadConfig() -> ghostty_config_t? {
|
static func loadConfig() -> ghostty_config_t? {
|
||||||
// Initialize the global configuration.
|
// Initialize the global configuration.
|
||||||
@ -140,19 +149,19 @@ extension Ghostty {
|
|||||||
AppDelegate.logger.critical("ghostty_config_new failed")
|
AppDelegate.logger.critical("ghostty_config_new failed")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load our configuration files from the home directory.
|
// Load our configuration files from the home directory.
|
||||||
ghostty_config_load_default_files(cfg);
|
ghostty_config_load_default_files(cfg);
|
||||||
ghostty_config_load_cli_args(cfg);
|
ghostty_config_load_cli_args(cfg);
|
||||||
ghostty_config_load_recursive_files(cfg);
|
ghostty_config_load_recursive_files(cfg);
|
||||||
|
|
||||||
// TODO: we'd probably do some config loading here... for now we'd
|
// TODO: we'd probably do some config loading here... for now we'd
|
||||||
// have to do this synchronously. When we support config updating we can do
|
// have to do this synchronously. When we support config updating we can do
|
||||||
// this async and update later.
|
// this async and update later.
|
||||||
|
|
||||||
// Finalize will make our defaults available.
|
// Finalize will make our defaults available.
|
||||||
ghostty_config_finalize(cfg)
|
ghostty_config_finalize(cfg)
|
||||||
|
|
||||||
// Log any configuration errors. These will be automatically shown in a
|
// Log any configuration errors. These will be automatically shown in a
|
||||||
// pop-up window too.
|
// pop-up window too.
|
||||||
let errCount = ghostty_config_errors_count(cfg)
|
let errCount = ghostty_config_errors_count(cfg)
|
||||||
@ -166,14 +175,14 @@ extension Ghostty {
|
|||||||
AppDelegate.logger.warning("config error: \(message)")
|
AppDelegate.logger.warning("config error: \(message)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the configuration errors (if any).
|
/// Returns the configuration errors (if any).
|
||||||
func configErrors() -> [String] {
|
func configErrors() -> [String] {
|
||||||
guard let cfg = self.config else { return [] }
|
guard let cfg = self.config else { return [] }
|
||||||
|
|
||||||
var errors: [String] = [];
|
var errors: [String] = [];
|
||||||
let errCount = ghostty_config_errors_count(cfg)
|
let errCount = ghostty_config_errors_count(cfg)
|
||||||
for i in 0..<errCount {
|
for i in 0..<errCount {
|
||||||
@ -181,70 +190,70 @@ extension Ghostty {
|
|||||||
let message = String(cString: err.message)
|
let message = String(cString: err.message)
|
||||||
errors.append(message)
|
errors.append(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
func appTick() {
|
func appTick() {
|
||||||
guard let app = self.app else { return }
|
guard let app = self.app else { return }
|
||||||
|
|
||||||
// Tick our app, which lets us know if we want to quit
|
// Tick our app, which lets us know if we want to quit
|
||||||
let exit = ghostty_app_tick(app)
|
let exit = ghostty_app_tick(app)
|
||||||
if (!exit) { return }
|
if (!exit) { return }
|
||||||
|
|
||||||
// We want to quit, start that process
|
// We want to quit, start that process
|
||||||
NSApplication.shared.terminate(nil)
|
NSApplication.shared.terminate(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reloadConfig() {
|
func reloadConfig() {
|
||||||
guard let app = self.app else { return }
|
guard let app = self.app else { return }
|
||||||
ghostty_app_reload_config(app)
|
ghostty_app_reload_config(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request that the given surface is closed. This will trigger the full normal surface close event
|
/// Request that the given surface is closed. This will trigger the full normal surface close event
|
||||||
/// cycle which will call our close surface callback.
|
/// cycle which will call our close surface callback.
|
||||||
func requestClose(surface: ghostty_surface_t) {
|
func requestClose(surface: ghostty_surface_t) {
|
||||||
ghostty_surface_request_close(surface)
|
ghostty_surface_request_close(surface)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTab(surface: ghostty_surface_t) {
|
func newTab(surface: ghostty_surface_t) {
|
||||||
let action = "new_tab"
|
let action = "new_tab"
|
||||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||||
AppDelegate.logger.warning("action failed action=\(action)")
|
AppDelegate.logger.warning("action failed action=\(action)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWindow(surface: ghostty_surface_t) {
|
func newWindow(surface: ghostty_surface_t) {
|
||||||
let action = "new_window"
|
let action = "new_window"
|
||||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||||
AppDelegate.logger.warning("action failed action=\(action)")
|
AppDelegate.logger.warning("action failed action=\(action)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func split(surface: ghostty_surface_t, direction: ghostty_split_direction_e) {
|
func split(surface: ghostty_surface_t, direction: ghostty_split_direction_e) {
|
||||||
ghostty_surface_split(surface, direction)
|
ghostty_surface_split(surface, direction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitMoveFocus(surface: ghostty_surface_t, direction: SplitFocusDirection) {
|
func splitMoveFocus(surface: ghostty_surface_t, direction: SplitFocusDirection) {
|
||||||
ghostty_surface_split_focus(surface, direction.toNative())
|
ghostty_surface_split_focus(surface, direction.toNative())
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitToggleZoom(surface: ghostty_surface_t) {
|
func splitToggleZoom(surface: ghostty_surface_t) {
|
||||||
let action = "toggle_split_zoom"
|
let action = "toggle_split_zoom"
|
||||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||||
AppDelegate.logger.warning("action failed action=\(action)")
|
AppDelegate.logger.warning("action failed action=\(action)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called when the selected keyboard changes. We have to notify Ghostty so that
|
// Called when the selected keyboard changes. We have to notify Ghostty so that
|
||||||
// it can reload the keyboard mapping for input.
|
// it can reload the keyboard mapping for input.
|
||||||
@objc private func keyboardSelectionDidChange(notification: NSNotification) {
|
@objc private func keyboardSelectionDidChange(notification: NSNotification) {
|
||||||
guard let app = self.app else { return }
|
guard let app = self.app else { return }
|
||||||
ghostty_app_keyboard_changed(app)
|
ghostty_app_keyboard_changed(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Ghostty Callbacks
|
// MARK: Ghostty Callbacks
|
||||||
|
|
||||||
static func newSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_direction_e, config: ghostty_surface_config_s) {
|
static func newSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_direction_e, config: ghostty_surface_config_s) {
|
||||||
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
||||||
NotificationCenter.default.post(name: Notification.ghosttyNewSplit, object: surface, userInfo: [
|
NotificationCenter.default.post(name: Notification.ghosttyNewSplit, object: surface, userInfo: [
|
||||||
@ -252,14 +261,14 @@ extension Ghostty {
|
|||||||
Notification.NewSurfaceConfigKey: config,
|
Notification.NewSurfaceConfigKey: config,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
static func closeSurface(_ userdata: UnsafeMutableRawPointer?, processAlive: Bool) {
|
static func closeSurface(_ userdata: UnsafeMutableRawPointer?, processAlive: Bool) {
|
||||||
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
||||||
NotificationCenter.default.post(name: Notification.ghosttyCloseSurface, object: surface, userInfo: [
|
NotificationCenter.default.post(name: Notification.ghosttyCloseSurface, object: surface, userInfo: [
|
||||||
"process_alive": processAlive,
|
"process_alive": processAlive,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
static func focusSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_focus_direction_e) {
|
static func focusSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_focus_direction_e) {
|
||||||
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
||||||
guard let splitDirection = SplitFocusDirection.from(direction: direction) else { return }
|
guard let splitDirection = SplitFocusDirection.from(direction: direction) else { return }
|
||||||
@ -271,16 +280,16 @@ extension Ghostty {
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func toggleSplitZoom(_ userdata: UnsafeMutableRawPointer?) {
|
static func toggleSplitZoom(_ userdata: UnsafeMutableRawPointer?) {
|
||||||
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
||||||
|
|
||||||
NotificationCenter.default.post(
|
NotificationCenter.default.post(
|
||||||
name: Notification.didToggleSplitZoom,
|
name: Notification.didToggleSplitZoom,
|
||||||
object: surface
|
object: surface
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func gotoTab(_ userdata: UnsafeMutableRawPointer?, n: Int32) {
|
static func gotoTab(_ userdata: UnsafeMutableRawPointer?, n: Int32) {
|
||||||
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
||||||
NotificationCenter.default.post(
|
NotificationCenter.default.post(
|
||||||
@ -291,59 +300,60 @@ extension Ghostty {
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func readClipboard(_ userdata: UnsafeMutableRawPointer?, location: ghostty_clipboard_e) -> UnsafePointer<CChar>? {
|
static func readClipboard(_ userdata: UnsafeMutableRawPointer?, location: ghostty_clipboard_e) -> UnsafePointer<CChar>? {
|
||||||
// We only support the standard clipboard
|
// We only support the standard clipboard
|
||||||
if (location != GHOSTTY_CLIPBOARD_STANDARD) { return nil }
|
if (location != GHOSTTY_CLIPBOARD_STANDARD) { return nil }
|
||||||
|
|
||||||
guard let appState = self.appState(fromSurface: userdata) else { return nil }
|
guard let surface = self.surfaceUserdata(from: userdata) else { return nil }
|
||||||
|
guard let appState = self.appState(fromView: surface) else { return nil }
|
||||||
guard let str = NSPasteboard.general.string(forType: .string) else { return nil }
|
guard let str = NSPasteboard.general.string(forType: .string) else { return nil }
|
||||||
|
|
||||||
// Ghostty requires we cache the string because the pointer we return has to remain
|
// Ghostty requires we cache the string because the pointer we return has to remain
|
||||||
// stable until the next call to readClipboard.
|
// stable until the next call to readClipboard.
|
||||||
appState.cached_clipboard_string = str
|
appState.cached_clipboard_string = str
|
||||||
return (str as NSString).utf8String
|
return (str as NSString).utf8String
|
||||||
}
|
}
|
||||||
|
|
||||||
static func writeClipboard(_ userdata: UnsafeMutableRawPointer?, string: UnsafePointer<CChar>?, location: ghostty_clipboard_e) {
|
static func writeClipboard(_ userdata: UnsafeMutableRawPointer?, string: UnsafePointer<CChar>?, location: ghostty_clipboard_e) {
|
||||||
// We only support the standard clipboard
|
// We only support the standard clipboard
|
||||||
if (location != GHOSTTY_CLIPBOARD_STANDARD) { return }
|
if (location != GHOSTTY_CLIPBOARD_STANDARD) { return }
|
||||||
|
|
||||||
guard let valueStr = String(cString: string!, encoding: .utf8) else { return }
|
guard let valueStr = String(cString: string!, encoding: .utf8) else { return }
|
||||||
let pb = NSPasteboard.general
|
let pb = NSPasteboard.general
|
||||||
pb.declareTypes([.string], owner: nil)
|
pb.declareTypes([.string], owner: nil)
|
||||||
pb.setString(valueStr, forType: .string)
|
pb.setString(valueStr, forType: .string)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func reloadConfig(_ userdata: UnsafeMutableRawPointer?) -> ghostty_config_t? {
|
static func reloadConfig(_ userdata: UnsafeMutableRawPointer?) -> ghostty_config_t? {
|
||||||
guard let newConfig = Self.loadConfig() else {
|
guard let newConfig = Self.loadConfig() else {
|
||||||
AppDelegate.logger.warning("failed to reload configuration")
|
AppDelegate.logger.warning("failed to reload configuration")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign the new config. This will automatically free the old config.
|
// Assign the new config. This will automatically free the old config.
|
||||||
// It is safe to free the old config from within this function call.
|
// It is safe to free the old config from within this function call.
|
||||||
let state = Unmanaged<AppState>.fromOpaque(userdata!).takeUnretainedValue()
|
let state = Unmanaged<AppState>.fromOpaque(userdata!).takeUnretainedValue()
|
||||||
state.config = newConfig
|
state.config = newConfig
|
||||||
|
|
||||||
// If we have a delegate, notify.
|
// If we have a delegate, notify.
|
||||||
if let delegate = state.delegate {
|
if let delegate = state.delegate {
|
||||||
delegate.configDidReload(state)
|
delegate.configDidReload(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newConfig
|
return newConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
static func wakeup(_ userdata: UnsafeMutableRawPointer?) {
|
static func wakeup(_ userdata: UnsafeMutableRawPointer?) {
|
||||||
let state = Unmanaged<AppState>.fromOpaque(userdata!).takeUnretainedValue()
|
let state = Unmanaged<AppState>.fromOpaque(userdata!).takeUnretainedValue()
|
||||||
|
|
||||||
// Wakeup can be called from any thread so we schedule the app tick
|
// Wakeup can be called from any thread so we schedule the app tick
|
||||||
// from the main thread. There is probably some improvements we can make
|
// from the main thread. There is probably some improvements we can make
|
||||||
// to coalesce multiple ticks but I don't think it matters from a performance
|
// to coalesce multiple ticks but I don't think it matters from a performance
|
||||||
// standpoint since we don't do this much.
|
// standpoint since we don't do this much.
|
||||||
DispatchQueue.main.async { state.appTick() }
|
DispatchQueue.main.async { state.appTick() }
|
||||||
}
|
}
|
||||||
|
|
||||||
static func setTitle(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer<CChar>?) {
|
static func setTitle(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer<CChar>?) {
|
||||||
let surfaceView = Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue()
|
let surfaceView = Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue()
|
||||||
guard let titleStr = String(cString: title!, encoding: .utf8) else { return }
|
guard let titleStr = String(cString: title!, encoding: .utf8) else { return }
|
||||||
@ -351,12 +361,12 @@ extension Ghostty {
|
|||||||
surfaceView.title = titleStr
|
surfaceView.title = titleStr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func setMouseShape(_ userdata: UnsafeMutableRawPointer?, shape: ghostty_mouse_shape_e) {
|
static func setMouseShape(_ userdata: UnsafeMutableRawPointer?, shape: ghostty_mouse_shape_e) {
|
||||||
let surfaceView = Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue()
|
let surfaceView = Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue()
|
||||||
surfaceView.setCursorShape(shape)
|
surfaceView.setCursorShape(shape)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func setMouseVisibility(_ userdata: UnsafeMutableRawPointer?, visible: Bool) {
|
static func setMouseVisibility(_ userdata: UnsafeMutableRawPointer?, visible: Bool) {
|
||||||
let surfaceView = Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue()
|
let surfaceView = Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue()
|
||||||
surfaceView.setCursorVisibility(visible)
|
surfaceView.setCursorVisibility(visible)
|
||||||
@ -372,10 +382,21 @@ extension Ghostty {
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func newTab(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) {
|
static func newTab(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) {
|
||||||
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
||||||
|
|
||||||
|
guard let appState = self.appState(fromView: surface) else { return }
|
||||||
|
guard appState.windowDecorations else {
|
||||||
|
let alert = NSAlert()
|
||||||
|
alert.messageText = "Tabs are disabled"
|
||||||
|
alert.informativeText = "Enable window decorations to use tabs"
|
||||||
|
alert.addButton(withTitle: "OK")
|
||||||
|
alert.alertStyle = .warning
|
||||||
|
_ = alert.runModal()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
NotificationCenter.default.post(
|
NotificationCenter.default.post(
|
||||||
name: Notification.ghosttyNewTab,
|
name: Notification.ghosttyNewTab,
|
||||||
object: surface,
|
object: surface,
|
||||||
@ -384,10 +405,10 @@ extension Ghostty {
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func newWindow(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) {
|
static func newWindow(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) {
|
||||||
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
||||||
|
|
||||||
NotificationCenter.default.post(
|
NotificationCenter.default.post(
|
||||||
name: Notification.ghosttyNewWindow,
|
name: Notification.ghosttyNewWindow,
|
||||||
object: surface,
|
object: surface,
|
||||||
@ -396,16 +417,15 @@ extension Ghostty {
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the GhosttyState from the given userdata value.
|
/// Returns the GhosttyState from the given userdata value.
|
||||||
static private func appState(fromSurface userdata: UnsafeMutableRawPointer?) -> AppState? {
|
static private func appState(fromView view: SurfaceView) -> AppState? {
|
||||||
let surfaceView = Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue()
|
guard let surface = view.surface else { return nil }
|
||||||
guard let surface = surfaceView.surface else { return nil }
|
|
||||||
guard let app = ghostty_surface_app(surface) else { return nil }
|
guard let app = ghostty_surface_app(surface) else { return nil }
|
||||||
guard let app_ud = ghostty_app_userdata(app) else { return nil }
|
guard let app_ud = ghostty_app_userdata(app) else { return nil }
|
||||||
return Unmanaged<AppState>.fromOpaque(app_ud).takeUnretainedValue()
|
return Unmanaged<AppState>.fromOpaque(app_ud).takeUnretainedValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the surface view from the userdata.
|
/// Returns the surface view from the userdata.
|
||||||
static private func surfaceUserdata(from userdata: UnsafeMutableRawPointer?) -> SurfaceView? {
|
static private func surfaceUserdata(from userdata: UnsafeMutableRawPointer?) -> SurfaceView? {
|
||||||
return Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue()
|
return Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue()
|
||||||
@ -428,7 +448,7 @@ extension EnvironmentValues {
|
|||||||
get { self[GhosttyAppKey.self] }
|
get { self[GhosttyAppKey.self] }
|
||||||
set { self[GhosttyAppKey.self] = newValue }
|
set { self[GhosttyAppKey.self] = newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
var ghosttyConfig: ghostty_config_t? {
|
var ghosttyConfig: ghostty_config_t? {
|
||||||
get { self[GhosttyConfigKey.self] }
|
get { self[GhosttyConfigKey.self] }
|
||||||
set { self[GhosttyConfigKey.self] = newValue }
|
set { self[GhosttyConfigKey.self] = newValue }
|
||||||
@ -439,7 +459,7 @@ extension View {
|
|||||||
func ghosttyApp(_ app: ghostty_app_t?) -> some View {
|
func ghosttyApp(_ app: ghostty_app_t?) -> some View {
|
||||||
environment(\.ghosttyApp, app)
|
environment(\.ghosttyApp, app)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ghosttyConfig(_ config: ghostty_config_t?) -> some View {
|
func ghosttyConfig(_ config: ghostty_config_t?) -> some View {
|
||||||
environment(\.ghosttyConfig, config)
|
environment(\.ghosttyConfig, config)
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import GhosttyKit
|
import GhosttyKit
|
||||||
|
|
||||||
class FullScreenHandler {
|
class FullScreenHandler { var previousTabGroup: NSWindowTabGroup?
|
||||||
var previousTabGroup: NSWindowTabGroup?
|
|
||||||
var previousTabGroupIndex: Int?
|
var previousTabGroupIndex: Int?
|
||||||
var previousContentFrame: NSRect?
|
var previousContentFrame: NSRect?
|
||||||
var isInFullscreen: Bool = false
|
var previousStyleMask: NSWindow.StyleMask? = nil
|
||||||
|
|
||||||
// We keep track of whether we entered non-native fullscreen in case
|
// We keep track of whether we entered non-native fullscreen in case
|
||||||
// a user goes to fullscreen, changes the config to disable non-native fullscreen
|
// a user goes to fullscreen, changes the config to disable non-native fullscreen
|
||||||
// and then wants to toggle it off
|
// and then wants to toggle it off
|
||||||
var isInNonNativeFullscreen: Bool = false
|
var isInNonNativeFullscreen: Bool = false
|
||||||
|
var isInFullscreen: Bool = false
|
||||||
|
|
||||||
func toggleFullscreen(window: NSWindow, nonNativeFullscreen: ghostty_non_native_fullscreen_e) {
|
func toggleFullscreen(window: NSWindow, nonNativeFullscreen: ghostty_non_native_fullscreen_e) {
|
||||||
let useNonNativeFullscreen = nonNativeFullscreen != GHOSTTY_NON_NATIVE_FULLSCREEN_FALSE
|
let useNonNativeFullscreen = nonNativeFullscreen != GHOSTTY_NON_NATIVE_FULLSCREEN_FALSE
|
||||||
@ -89,6 +89,7 @@ class FullScreenHandler {
|
|||||||
|
|
||||||
// This is important: it gives us the full screen, including the
|
// This is important: it gives us the full screen, including the
|
||||||
// notch area on MacBooks.
|
// notch area on MacBooks.
|
||||||
|
self.previousStyleMask = window.styleMask
|
||||||
window.styleMask.remove(.titled)
|
window.styleMask.remove(.titled)
|
||||||
|
|
||||||
// Set frame to screen size, accounting for the menu bar if needed
|
// Set frame to screen size, accounting for the menu bar if needed
|
||||||
@ -126,8 +127,8 @@ class FullScreenHandler {
|
|||||||
func leaveFullscreen(window: NSWindow) {
|
func leaveFullscreen(window: NSWindow) {
|
||||||
guard let previousFrame = previousContentFrame else { return }
|
guard let previousFrame = previousContentFrame else { return }
|
||||||
|
|
||||||
// Restore title bar
|
// Restore the style mask
|
||||||
window.styleMask.insert(.titled)
|
window.styleMask = self.previousStyleMask!
|
||||||
|
|
||||||
// Restore previous presentation options
|
// Restore previous presentation options
|
||||||
NSApp.presentationOptions = []
|
NSApp.presentationOptions = []
|
||||||
|
@ -242,7 +242,6 @@ keybind: Keybinds = .{},
|
|||||||
|
|
||||||
/// If false, windows won't have native decorations, i.e. titlebar and
|
/// If false, windows won't have native decorations, i.e. titlebar and
|
||||||
/// borders.
|
/// borders.
|
||||||
/// Currently only supported with GTK.
|
|
||||||
@"window-decoration": bool = true,
|
@"window-decoration": bool = true,
|
||||||
|
|
||||||
/// Whether to allow programs running in the terminal to read/write to
|
/// Whether to allow programs running in the terminal to read/write to
|
||||||
|
Reference in New Issue
Block a user