Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an option to use the Windows title bar #912

Merged
merged 1 commit into from
Oct 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/src/lib/app-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { WindowState } from './window-state'
import { Shell } from './shells'

import { ApplicableTheme, ApplicationTheme } from '../ui/lib/application-theme'
import { TitleBarStyle } from '../ui/lib/title-bar-style'
import { IAccountRepositories } from './stores/api-repositories-store'
import { ManualConflictResolution } from '../models/manual-conflict-resolution'
import { Banner } from '../models/banner'
Expand Down Expand Up @@ -275,6 +276,9 @@ export interface IAppState {
/** The currently applied appearance (aka theme) */
readonly currentTheme: ApplicableTheme

/** The selected title bar style for the application */
readonly titleBarStyle: TitleBarStyle

/**
* A map keyed on a user account (GitHub.com or GitHub Enterprise)
* containing an object with repositories that the authenticated
Expand Down
48 changes: 48 additions & 0 deletions app/src/lib/get-title-bar-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { writeFile } from 'fs/promises'
import { existsSync, readFileSync } from 'fs'
import { join } from 'path'
import { app } from 'electron'
import { TitleBarStyle } from '../ui/lib/title-bar-style'

export type TitleBarConfig = {
titleBarStyle: TitleBarStyle
}

let cachedTitleBarConfig: TitleBarConfig | null = null

// The function has to be synchronous,
// since we need its return value to create electron BrowserWindow
export function readTitleBarConfigFileSync(): TitleBarConfig {
if (cachedTitleBarConfig) {
return cachedTitleBarConfig
}

const titleBarConfigPath = getTitleBarConfigPath()

if (existsSync(titleBarConfigPath)) {
const storedTitleBarConfig = JSON.parse(
readFileSync(titleBarConfigPath, 'utf8')
)

if (
storedTitleBarConfig.titleBarStyle === 'native' ||
storedTitleBarConfig.titleBarStyle === 'custom'
) {
cachedTitleBarConfig = storedTitleBarConfig
}
}

// Cache the default value if the config file is not found, or if it contains an invalid value.
if (cachedTitleBarConfig == null) {
cachedTitleBarConfig = { titleBarStyle: 'native' }
}

return cachedTitleBarConfig
}

export function saveTitleBarConfigFile(config: TitleBarConfig) {
return writeFile(getTitleBarConfigPath(), JSON.stringify(config), 'utf8')
}

const getTitleBarConfigPath = () =>
join(app.getPath('userData'), '.title-bar-config')
4 changes: 4 additions & 0 deletions app/src/lib/ipc-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Architecture } from './get-architecture'
import { EndpointToken } from './endpoint-token'
import { PathType } from '../ui/lib/app-proxy'
import { ThemeSource } from '../ui/lib/theme-source'
import { TitleBarStyle } from '../ui/lib/title-bar-style'
import { DesktopNotificationPermission } from 'desktop-notifications/dist/notification-permission'
import { NotificationCallback } from 'desktop-notifications/dist/notification-callback'
import { DesktopAliveEvent } from './stores/alive-store'
Expand Down Expand Up @@ -65,6 +66,7 @@ export type RequestChannels = {
blur: () => void
'update-accounts': (accounts: ReadonlyArray<EndpointToken>) => void
'quit-and-install-updates': () => void
'restart-app': () => void
'quit-app': () => void
'minimize-window': () => void
'maximize-window': () => void
Expand Down Expand Up @@ -121,6 +123,8 @@ export type RequestResponseChannels = {
'should-use-dark-colors': () => Promise<boolean>
'save-guid': (guid: string) => Promise<void>
'get-guid': () => Promise<string>
'save-title-bar-style': (titleBarStyle: TitleBarStyle) => Promise<void>
'get-title-bar-style': () => Promise<TitleBarStyle>
'show-notification': (
title: string,
body: string,
Expand Down
15 changes: 15 additions & 0 deletions app/src/lib/stores/app-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ import {
getPersistedThemeName,
setPersistedTheme,
} from '../../ui/lib/application-theme'
import { TitleBarStyle } from '../../ui/lib/title-bar-style'
import {
getAppMenu,
getCurrentWindowState,
Expand All @@ -91,6 +92,8 @@ import {
sendWillQuitEvenIfUpdatingSync,
quitApp,
sendCancelQuittingSync,
saveTitleBarStyle,
getTitleBarStyle,
} from '../../ui/main-process-proxy'
import {
API,
Expand Down Expand Up @@ -511,6 +514,7 @@ export class AppStore extends TypedBaseStore<IAppState> {
private selectedBranchesTab = BranchesTab.Branches
private selectedTheme = ApplicationTheme.System
private currentTheme: ApplicableTheme = ApplicationTheme.Light
private titleBarStyle: TitleBarStyle = 'native'

private useWindowsOpenSSH: boolean = false

Expand Down Expand Up @@ -992,6 +996,7 @@ export class AppStore extends TypedBaseStore<IAppState> {
selectedBranchesTab: this.selectedBranchesTab,
selectedTheme: this.selectedTheme,
currentTheme: this.currentTheme,
titleBarStyle: this.titleBarStyle,
apiRepositories: this.apiRepositoriesStore.getState(),
useWindowsOpenSSH: this.useWindowsOpenSSH,
optOutOfUsageTracking: this.statsStore.getOptOut(),
Expand Down Expand Up @@ -2170,6 +2175,8 @@ export class AppStore extends TypedBaseStore<IAppState> {
this.emitUpdate()
})

this.titleBarStyle = await getTitleBarStyle()

this.lastThankYou = getObject<ILastThankYou>(lastThankYouKey)

this.pullRequestSuggestedNextAction =
Expand Down Expand Up @@ -6461,6 +6468,14 @@ export class AppStore extends TypedBaseStore<IAppState> {
return Promise.resolve()
}

/**
* Set the title bar style for the application
*/
public _setTitleBarStyle(titleBarStyle: TitleBarStyle) {
this.titleBarStyle = titleBarStyle
return saveTitleBarStyle(titleBarStyle)
}

public async _resolveCurrentEditor() {
const match = await findEditorOrDefault(this.selectedExternalEditor)
const resolvedExternalEditor = match != null ? match.editor : null
Expand Down
4 changes: 4 additions & 0 deletions app/src/main-process/app-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
getWindowState,
registerWindowStateChangedEvents,
} from '../lib/window-state'
import { readTitleBarConfigFileSync } from '../lib/get-title-bar-config'
import { MenuEvent } from './menu'
import { URLActionType } from '../lib/parse-app-url'
import { ILaunchStats } from '../lib/stats'
Expand Down Expand Up @@ -75,6 +76,9 @@ export class AppWindow {
} else if (__WIN32__) {
windowOptions.frame = false
} else if (__LINUX__) {
if (readTitleBarConfigFileSync().titleBarStyle === 'custom') {
windowOptions.frame = false
}
windowOptions.icon = join(__dirname, 'static', 'logos', '512x512.png')

// relax restriction here for users trying to run app at a small
Expand Down
19 changes: 19 additions & 0 deletions app/src/main-process/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ import {
} from '../lib/get-architecture'
import { buildSpellCheckMenu } from './menu/build-spell-check-menu'
import { getMainGUID, saveGUIDFile } from '../lib/get-main-guid'
import {
readTitleBarConfigFileSync,
saveTitleBarConfigFile,
} from '../lib/get-title-bar-config'
import {
getNotificationsPermission,
requestNotificationsPermission,
Expand Down Expand Up @@ -505,6 +509,11 @@ app.on('ready', () => {
mainWindow?.quitAndInstallUpdate()
)

ipcMain.on('restart-app', () => {
app.relaunch()
app.exit()
})

ipcMain.on('quit-app', () => app.quit())

ipcMain.on('minimize-window', () => mainWindow?.minimizeWindow())
Expand Down Expand Up @@ -685,6 +694,16 @@ app.on('ready', () => {

ipcMain.handle('save-guid', (_, guid) => saveGUIDFile(guid))

ipcMain.handle(
'get-title-bar-style',
async () => readTitleBarConfigFileSync().titleBarStyle
)

ipcMain.handle(
'save-title-bar-style',
async (_, titleBarStyle) => await saveTitleBarConfigFile({ titleBarStyle })
)

ipcMain.handle('show-notification', async (_, title, body, userInfo) =>
showNotification(title, body, userInfo)
)
Expand Down
2 changes: 2 additions & 0 deletions app/src/models/popup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export enum PopupType {
PullRequestComment = 'PullRequestComment',
UnknownAuthors = 'UnknownAuthors',
ConfirmRepoRulesBypass = 'ConfirmRepoRulesBypass',
ConfirmRestart = 'ConfirmRestart',
}

interface IBasePopup {
Expand Down Expand Up @@ -422,5 +423,6 @@ export type PopupDetail =
branch: string
onConfirm: () => void
}
| { type: PopupType.ConfirmRestart }

export type Popup = IBasePopup & PopupDetail
20 changes: 13 additions & 7 deletions app/src/ui/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import { Welcome } from './welcome'
import { AppMenuBar } from './app-menu'
import { UpdateAvailable, renderBanner } from './banners'
import { Preferences } from './preferences'
import { ConfirmRestart } from './preferences/confirm-restart'
import { RepositorySettings } from './repository-settings'
import { AppError } from './app-error'
import { MissingRepository } from './missing-repository'
Expand Down Expand Up @@ -1364,8 +1365,8 @@ export class App extends React.Component<IAppProps, IAppState> {
* on Windows.
*/
private renderAppMenuBar() {
// We only render the app menu bar on Windows
if (!__WIN32__) {
// We do not render the app menu bar on macOS
if (__DARWIN__) {
return null
}

Expand Down Expand Up @@ -1416,22 +1417,22 @@ export class App extends React.Component<IAppProps, IAppState> {
this.state.currentFoldout &&
this.state.currentFoldout.type === FoldoutType.AppMenu

// As Linux still uses the classic Electron menu, we are opting out of the
// custom menu that is shown as part of the title bar below
if (__LINUX__) {
// We do not render the app menu bar on Linux when the user has selected
// the "native" menu option
if (__LINUX__ && this.state.titleBarStyle === 'native') {
return null
}

// When we're in full-screen mode on Windows we only need to render
// the title bar when the menu bar is active. On other platforms we
// never render the title bar while in full-screen mode.
if (inFullScreen) {
if (!__WIN32__ || !menuBarActive) {
if (__DARWIN__ || !menuBarActive) {
return null
}
}

const showAppIcon = __WIN32__ && !this.state.showWelcomeFlow
mon-jai marked this conversation as resolved.
Show resolved Hide resolved
const showAppIcon = !__DARWIN__ && !this.state.showWelcomeFlow
const inWelcomeFlow = this.state.showWelcomeFlow
const inNoRepositoriesView = this.inNoRepositoriesViewState()

Expand Down Expand Up @@ -1623,6 +1624,7 @@ export class App extends React.Component<IAppProps, IAppState> {
onDismissed={onPopupDismissedFn}
selectedShell={this.state.selectedShell}
selectedTheme={this.state.selectedTheme}
titleBarStyle={this.state.titleBarStyle}
repositoryIndicatorsEnabled={this.state.repositoryIndicatorsEnabled}
/>
)
Expand Down Expand Up @@ -2520,6 +2522,10 @@ export class App extends React.Component<IAppProps, IAppState> {
/>
)
}

case PopupType.ConfirmRestart: {
return <ConfirmRestart onDismissed={onPopupDismissedFn} />
}
default:
return assertNever(popup, `Unknown popup type: ${popup}`)
}
Expand Down
9 changes: 9 additions & 0 deletions app/src/ui/dispatcher/dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import { TipState, IValidBranch } from '../../models/tip'
import { Banner, BannerType } from '../../models/banner'

import { ApplicationTheme } from '../lib/application-theme'
import { TitleBarStyle } from '../lib/title-bar-style'
import { installCLI } from '../lib/install-cli'
import {
executeMenuItem,
Expand Down Expand Up @@ -2475,6 +2476,14 @@ export class Dispatcher {
return this.appStore._setSelectedTheme(theme)
}

/**
* Set the title bar style for the application
*/
public async setTitleBarStyle(titleBarStyle: TitleBarStyle) {
await this.appStore._setTitleBarStyle(titleBarStyle)
this.showPopup({ type: PopupType.ConfirmRestart })
}

/**
* Increments either the `repoWithIndicatorClicked` or
* the `repoWithoutIndicatorClicked` metric
Expand Down
17 changes: 17 additions & 0 deletions app/src/ui/lib/title-bar-style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* This string enum represents the supported modes for rendering the title bar
* in the app.
*
* - 'native' - Use the default window style and chrome supported by the window
* manager
*
* - 'custom' - Hide the default window style and chrome and display the menu
* provided by GitHub Desktop
*
* This is only available on the Linux build. For other operating systems this
* is not configurable:
*
* - macOS uses the native title bar
* - Windows uses the custom title bar
*/
export type TitleBarStyle = 'native' | 'custom'
mon-jai marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 7 additions & 0 deletions app/src/ui/main-process-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ export const checkForUpdates = invokeProxy('check-for-updates', 1)
/** Tell the main process to quit the app and install updates */
export const quitAndInstallUpdate = sendProxy('quit-and-install-updates', 0)

/** Tell the main process to restart the app */
export const restartApp = sendProxy('restart-app', 0)

/** Tell the main process to quit the app */
export const quitApp = sendProxy('quit-app', 0)

Expand Down Expand Up @@ -379,6 +382,10 @@ export const showOpenDialog = invokeProxy('show-open-dialog', 1)
export const saveGUID = invokeProxy('save-guid', 1)
export const getGUID = invokeProxy('get-guid', 0)

/** Tell the main process read/save the the title bar style */
export const saveTitleBarStyle = invokeProxy('save-title-bar-style', 1)
export const getTitleBarStyle = invokeProxy('get-title-bar-style', 0)

/** Tell the main process to show a notification */
export const showNotification = invokeProxy('show-notification', 3)

Expand Down
Loading