Add app bar for nody-greeter

pull/4/head
Warinyourself 3 years ago
parent 21ef3b0cdb
commit 39722d6614

@ -12,6 +12,8 @@ module.exports = {
indent: 'off',
'multiline-ternary': 'off',
'space-before-function-paren': [2, 'never'],
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'warn',
'vue/array-bracket-spacing': 'error',
'vue/arrow-spacing': 'error',
'vue/block-spacing': 'error',

@ -12,7 +12,7 @@ yay -S lightdm-webkit-theme-osmos
### Manual installation
Or set `greeter-session=lightdm-webkit2-greeter` in `/etc/lightdm/lightdm.conf`, then edit `/etc/lightdm/lightdm-webkit2-greeter.conf` to set `webkit_theme=lightdm-webkit-theme-osmos`.
Or set `greeter-session=lightdm-webkit2-greeter` in `/etc/lightdm/lightdm.conf`, then edit `/etc/lightdm/lightdm-webkit2-greeter.conf` to set `webkit_theme=osmos`.
## Themes
### [Random](https://warinyourself.github.io/lightdm-webkit-theme-osmos/?pxratio=0.8&animation-speed=5&symmetry=0.01&thickness=0.1&hue=360&brightness=1&invert=false&blur=false&no-transition=false&show-framerate=false&only-ui=true&themeName=Random)
@ -35,3 +35,6 @@ Or set `greeter-session=lightdm-webkit2-greeter` in `/etc/lightdm/lightdm.conf`,
### [Infinity](https://warinyourself.github.io/lightdm-webkit-theme-osmos/?palette=3&size=11&amount=50&animation-speed=20&blur=false&no-transition=false&show-framerate=false&only-ui=true&themeName=Infinity)
![Infinity](https://user-images.githubusercontent.com/83131232/153943210-e4cc3bc3-3ade-4323-a216-acf787b61d76.png)
### Roadmap
- Integrates with

@ -0,0 +1,66 @@
const child_process = require('child_process')
// import * as child_process from 'child_process'
// import { logger } from '../logger'
const logger = {
error: console.error,
debug: console.log
}
type Callback = (data: string) => void;
class ACPIController {
public constructor() {
if (this.checkAcpi()) this.listen()
else logger.error('ACPI: acpi_listen does not exists')
}
protected tries = 0;
protected callbacks: Callback[] = [];
public connect(cb: Callback): void {
this.callbacks.push(cb)
}
public disconnect(cb: Callback): void {
const ind = this.callbacks.findIndex((c) => {
return c === cb
})
if (ind == -1) return
this.callbacks.splice(ind, 1)
}
private checkAcpi(): boolean {
const res = child_process.spawnSync('which', ['acpi_listen'], {
encoding: 'utf-8'
})
if (res.status == 0) return true
else return false
}
private listen(): void {
const acpi = child_process.spawn('acpi_listen')
acpi.on('error', (err) => {
logger.error('ACPI: ' + err.message)
})
acpi.on('close', () => {
if (this.tries < 5) {
this.tries++
logger.debug('Restarting acpi_listen')
return this.listen()
}
})
acpi.stdout.addListener('data', (d: Buffer) => {
const data = d.toString().trim()
this.callbacks.forEach((cb) => {
console.log({ data })
if (cb !== undefined) cb(data)
})
})
}
}
const ACPI = new ACPIController()
export { ACPI }

@ -4,6 +4,7 @@ import { AppModule } from '@/store/app'
import { PageModule } from './store/page'
import { Debounce, focusInputPassword } from './utils/helper'
import { hotkeys } from '@/utils/hotkeys'
import { initTimer } from './utils/time'
@Component
export default class MainApp extends Vue {
@ -24,6 +25,7 @@ export default class MainApp extends Vue {
created() {
AppModule.setUpSettings()
this.initKeybinds()
initTimer()
}
initKeybinds() {

@ -0,0 +1,35 @@
import { AppModule } from '@/store/app'
import { Component, Vue } from 'vue-property-decorator'
import AppIcon from './AppIcon.vue'
import timer from '@/utils/time'
import BatteryIcon from '../base/BatteryIcon'
@Component({
components: { AppIcon, BatteryIcon }
})
export default class AppSelector extends Vue {
get batteryLevel() {
return AppModule.batteryLevel
}
get brightLevel() {
return AppModule.brightness
}
get currentTime() {
return timer.longTime
}
render() {
return <div class="app-bar">
<div class="app-bar__time"> { timer.longTime } </div>
<div class="app-bar__info">
<BatteryIcon />
<div class="app-bar__bright">
<AppIcon name="brightness" class="brightness-icon"/>
{ this.brightLevel }
</div>
</div>
</div>
}
}

@ -48,6 +48,57 @@
/>
</svg>
<svg
v-else-if="name === 'charge'"
width="29"
height="12"
viewBox="0 0 29 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12.7573 6.98762L12.142 0.419995C12.1171 0.15406 12.3907 -0.0543317 12.6768 0.0126378L28.8393 3.79557C29.0764 3.85104 29.0417 4.16413 28.7973 4.1762L17.2789 4.74482C17.0429 4.75647 16.8619 4.9383 16.8741 5.15137L17.2434 11.5926C17.2584 11.8538 16.9882 12.053 16.7077 11.9874L0.160717 8.11679C-0.0792939 8.06065 -0.0397734 7.74266 0.208049 7.73595L12.3429 7.40719C12.5887 7.40053 12.778 7.2089 12.7573 6.98762Z"
fill="currentColor"
/>
</svg>
<svg
v-else-if="name === 'brightness'"
width="128"
height="128"
viewBox="0 0 128 128"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M64 103.226C60.5795 103.226 57.8065 105.999 57.8065 109.419V121.806C57.8065 125.227 60.5795 128 64 128C67.4205 128 70.1935 125.227 70.1935 121.807V109.42C70.1935 105.999 67.4205 103.226 64 103.226Z"
/>
<path
d="M64 0C60.5795 0 57.8065 2.773 57.8065 6.1935V18.5805C57.8065 22.001 60.5795 24.774 64 24.774C67.4205 24.774 70.1935 22.001 70.1935 18.5805V6.1935C70.1935 2.773 67.4205 0 64 0Z"
/>
<path
d="M64.0007 37.1613C49.2027 37.1618 37.163 49.2015 37.163 64C37.163 78.7985 49.202 90.838 64 90.8388C78.7992 90.8388 90.8402 78.799 90.8402 64C90.8402 49.201 78.8 37.1613 64.0007 37.1613Z"
/>
<path
d="M128 64C128 60.5795 125.227 57.8065 121.807 57.8065H109.42C105.999 57.8065 103.226 60.5795 103.226 64C103.226 67.4205 105.999 70.1935 109.42 70.1935H121.807C125.227 70.1935 128 67.4205 128 64Z"
/>
<path
d="M0 64C0 67.4205 2.773 70.1935 6.1935 70.1935H18.5805C22.001 70.1935 24.774 67.4205 24.774 64C24.774 60.5795 22.001 57.8065 18.5805 57.8065H6.1935C2.773 57.8065 0 60.5795 0 64Z"
/>
<path
d="M96.116 38.077C97.7007 38.077 99.2862 37.472 100.495 36.2627L109.255 27.5037C111.673 25.085 111.673 21.1635 109.255 18.7447C106.836 16.326 102.915 16.326 100.495 18.7447L91.7363 27.5037C89.3175 29.9225 89.3175 33.844 91.7363 36.2627C92.9463 37.4722 94.531 38.077 96.116 38.077Z"
/>
<path
d="M23.1248 111.069C24.71 111.069 26.295 110.464 27.504 109.255L36.2632 100.496C38.682 98.0775 38.682 94.1555 36.2632 91.7367C33.8447 89.318 29.923 89.318 27.5042 91.7367L18.745 100.496C16.3263 102.914 16.3263 106.836 18.745 109.255C19.9543 110.464 21.5395 111.069 23.1248 111.069Z"
/>
<path
d="M100.496 91.7367C98.0775 89.318 94.1555 89.318 91.7367 91.7367C89.318 94.155 89.318 98.077 91.7367 100.496L100.496 109.255C101.706 110.464 103.291 111.069 104.875 111.069C106.461 111.069 108.046 110.464 109.255 109.255C111.673 106.837 111.673 102.915 109.255 100.496L100.496 91.7367Z"
/>
<path
d="M27.5042 36.2627C28.7137 37.4722 30.2988 38.077 31.884 38.077C33.4693 38.077 35.0542 37.472 36.2632 36.2627C38.682 33.844 38.682 29.9225 36.2632 27.5037L27.504 18.7445C25.0857 16.326 21.1638 16.326 18.745 18.7445C16.3263 21.1632 16.3263 25.0847 18.745 27.5035L27.5042 36.2627Z"
/>
</svg>
<svg
v-else-if="name === 'suspend'"
xmlns="http://www.w3.org/2000/svg"

@ -86,14 +86,16 @@ export default class AppMenu extends Vue {
event.preventDefault()
}
buildElementItem(item: AppMenuItem, index: number) {
buildElementItem(item: AppMenuItem | string, index: number) {
const isSting = typeof item === 'string'
const { text, icon } = isSting ? { text: item, icon: null } : item
return <li
class='menu-list-item'
key={index}
onClick={() => { this.handleCallback(item) }}
>
{ item.text }
{ item.icon && <AppIcon class='menu-icon' name={item.icon} /> }
{ text }
{ icon && <AppIcon class='menu-icon' name={icon} /> }
</li>
}

@ -19,12 +19,13 @@ export default class AppSelector extends Vue implements AppSelectorProps {
selectedValue: null | AppMenuItem | string = null
get fullItem() {
get fullSelectedItem() {
const selected = this.value !== null ? this.value : this.selectedValue
const finalSelectedValue = typeof selected === 'object' ? selected?.value : selected
return this.items.find(({ value }) => {
const finalValue = typeof selected === 'object' ? selected?.value : selected
return value === finalValue
return this.items.find((item) => {
const finalValue = typeof item === 'object' ? item?.value : item
return finalValue === finalSelectedValue
})
}
@ -37,7 +38,7 @@ export default class AppSelector extends Vue implements AppSelectorProps {
}
get currentValueLabel() {
const selected = this.fullItem ?? this.selectedValue
const selected = this.fullSelectedItem ?? this.selectedValue
return typeof selected === 'object' ? selected?.text : selected
}
@ -65,10 +66,10 @@ export default class AppSelector extends Vue implements AppSelectorProps {
}
render() {
const selectorIcon = this.fullItem?.icon ? <AppIcon {...{
const selectorIcon = this.fullSelectedItem?.icon ? <AppIcon {...{
class: 'menu-icon selector-icon',
props: {
name: this.fullItem.icon
name: this.fullSelectedItem.icon
}
}}/> : null

@ -0,0 +1,29 @@
import { AppModule } from '@/store/app'
import { Component, Vue } from 'vue-property-decorator'
import AppIcon from '@/components/app/AppIcon.vue'
@Component({
components: { AppIcon }
})
export default class BatteryIcon extends Vue {
get batteryLevel() {
return AppModule.batteryLevel
}
get isCharging() {
return AppModule.isCharging
}
render() {
return <div class="app-bar-battery">
<div class="app-bar-battery-icon">
<div
class={`app-bar-battery-icon__fill ${this.isCharging ? 'charging' : ''}`}
style={{ width: `${this.batteryLevel}%` }}
/>
{ this.isCharging && <AppIcon name="charge" class="app-bar-battery-icon__charge"/>}
</div>
<span class="app-bar-battery__percent"> { this.batteryLevel }% </span>
</div>
}
}

@ -4,30 +4,14 @@ import { AppModule } from '@/store/app'
import AppIcon from '@/components/app/AppIcon.vue'
import { LightdmUsers } from '@/models/lightdm'
import { PageModule } from '@/store/page'
import { DateTimeFormatOptions } from 'vue-i18n'
import timer from '@/utils/time'
@Component({
components: { AppIcon }
})
export default class UserAvatar extends Vue {
updater = new Date().getTime()
get currentTime() {
const options: DateTimeFormatOptions = {
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: false
}
if (this.isOpenSettings) {
options.month = 'long'
options.day = 'numeric'
options.weekday = 'short'
}
return new Intl.DateTimeFormat(this.locale, options).format(new Date())
return this.isOpenSettings ? timer.longTime : timer.shortTime
}
get isOpenSettings() {
@ -46,12 +30,6 @@ export default class UserAvatar extends Vue {
return AppModule.users
}
mounted() {
this.updater = new Date().getTime()
setInterval(() => { this.updater = new Date().getTime() }, 1000)
}
buildUserAvatar(image: string | undefined) {
const defaultAvatar = <AppIcon name='user'/>
const userAvatar = <div
@ -64,7 +42,7 @@ export default class UserAvatar extends Vue {
buildUser(user: LightdmUsers) {
return <div class='user-choice' key={ user.username }>
<p class='time' key={ this.updater }> { this.currentTime } </p>
<p class='time'> { this.currentTime } </p>
{ this.buildUserAvatar(user?.image) }
<div class='user-name'> { user?.display_name } </div>
</div>

@ -1,7 +1,7 @@
import { Component, Vue } from 'vue-property-decorator'
import { AppModule } from '@/store/app'
import { AppInputButton, AppInputColor, AppInputThemeGeneral, AppInputThemeSlider, AppTheme } from '@/models/app'
import { AppInputButton, AppInputColor, AppInputThemeGeneral, AppInputThemePalette, AppInputThemeSelector, AppInputThemeSlider, AppTheme } from '@/models/app'
import AppSlider from '@/components/app/AppSlider'
import AppButton from '@/components/app/AppButton'
@ -53,6 +53,16 @@ export default class SettingsCustom extends Vue {
return <AppSlider {...{ props, on: { input: handler } } } />
}
buildSelector(input: AppInputThemeSelector) {
const { label, value, values } = input
const props = { label, value, items: values }
const handler = (value: number) => {
AppModule.changeSettingsThemeInput({ key: input.name, value })
}
return <AppSelector {...{ props, on: { input: handler } } } />
}
buildColor(input: AppInputThemeGeneral) {
const { label, value, options } = input
const props = { label, value }
@ -81,7 +91,7 @@ export default class SettingsCustom extends Vue {
return <AppCheckbox {...{ props, on: { input: handler } } }/>
}
buildPalette(input: AppInputThemeGeneral) {
buildPalette(input: AppInputThemePalette) {
const { label, value, values } = input
const props = { label, value, values }
const handler = (value: boolean) => {
@ -109,7 +119,8 @@ export default class SettingsCustom extends Vue {
slider: this.buildSlider,
checkbox: this.buildCheckbox,
palette: this.buildPalette,
button: this.buildButton
button: this.buildButton,
selector: this.buildSelector
}
return <div class='user-settings-custom'>

@ -34,6 +34,10 @@ export default class SettingsGeneral extends Vue {
if (hasQyery) { this.$router.replace({}) }
}
changeTheme() {
throw Error('Change theme')
}
render() {
return <div class='user-settings-general'>
<SettingsCheckboxes />
@ -43,9 +47,12 @@ export default class SettingsGeneral extends Vue {
<SettingsHotkeys />
<div class="help-block">
<AppButton onClick={ this.resetSettings } block>
<AppButton onClick={ this.resetSettings } block class="mb-2">
{ this.$t('settings.reset-settings') }
</AppButton>
<AppButton onClick={ this.changeTheme } block>
{ this.$t('settings.change-theme') }
</AppButton>
</div>
</div>
}

@ -3,7 +3,8 @@ import { AppModule } from '@/store/app'
import { Wavery } from './lib/wave'
import { Debounce } from '@/utils/helper'
import { changeHsl, hexToRgb, rgbToHsl } from '@/utils/color'
import { AppInputThemeGeneral } from '@/models/app'
import { AppInputThemePalette } from '@/models/app'
import { AgidaTypes } from '@/utils/constant'
export const OPACITY_ARR = [0.265, 0.4, 0.53, 1]
export const topColors = ['#03C79C', '#00A5B2', '#0080A5', '#005A8D']
@ -20,11 +21,7 @@ export default class AgidaTheme extends Vue {
width: 1200,
segmentCount: 5,
layerCount: 4,
variance: 1,
animation: {
steps: 2,
time: 40000
}
variance: 1
}
screen = {
@ -33,7 +30,7 @@ export default class AgidaTheme extends Vue {
}
get palette(): string[] {
const input = AppModule.getThemeInput('palette') as AppInputThemeGeneral
const input = AppModule.getThemeInput('palette') as AppInputThemePalette
const index = input?.value as number || 0
const values = input?.values || ['#00CC99', '#6600FF']
@ -52,6 +49,10 @@ export default class AgidaTheme extends Vue {
return AppModule.getThemeInput('animation-speed')?.value as number || 40
}
get type(): AgidaTypes {
return AppModule.getThemeInput('type')?.value as AgidaTypes || 'agida'
}
get topColors(): string[] {
const initHSL = rgbToHsl(hexToRgb(this.topColor))
const second = changeHsl(initHSL, 10, -3, -5)
@ -71,13 +72,13 @@ export default class AgidaTheme extends Vue {
}
get bottomActiveColor(): string {
return this.bottomColor // this.bottomColors[3]
return this.bottomColor
}
get knifeSVG(): JSX.Element {
return <g>
<path d="M1233.68 556.289L490.85 201.979L400.013 392.424C1142.84 746.735 1233.68 556.289 1233.68 556.289Z" fill="url(#paint0_linear_602_754)"/>
<path d="M432.556 293.998L479.117 196.382L74.764 3.5156C59.8095 -3.61734 41.904 2.72332 34.7711 17.6778L3.55143 83.1312C-3.58151 98.0857 2.75915 115.991 17.7137 123.124L52.0107 139.483C65.6195 145.974 81.9138 140.204 88.4048 126.595C88.4048 126.595 102.294 97.911 145.302 118.425C188.31 138.938 173.495 170.432 173.495 170.432C173.495 170.432 188.648 139.1 231.655 159.613C274.663 180.127 259.849 211.621 259.849 211.621C259.849 211.621 274.692 180.141 318.009 200.802C361.326 221.463 346.203 252.809 346.203 252.809C346.203 252.809 361.355 221.477 404.363 241.991C447.371 262.504 432.556 293.998 432.556 293.998Z" fill="url(#paint1_linear_602_754)"/>
return <g style="transform: scale(0.7) translate(-27%, -7%);transform-origin: center">
<path d="M1233.68 556.289L490.85 201.979L400.013 392.424C1142.84 746.735 1233.68 556.289 1233.68 556.289Z" fill="white"/>
<path d="M432.556 293.998L479.117 196.382L74.764 3.5156C59.8095 -3.61734 41.904 2.72332 34.7711 17.6778L3.55143 83.1312C-3.58151 98.0857 2.75915 115.991 17.7137 123.124L52.0107 139.483C65.6195 145.974 81.9138 140.204 88.4048 126.595C88.4048 126.595 102.294 97.911 145.302 118.425C188.31 138.938 173.495 170.432 173.495 170.432C173.495 170.432 188.648 139.1 231.655 159.613C274.663 180.127 259.849 211.621 259.849 211.621C259.849 211.621 274.692 180.141 318.009 200.802C361.326 221.463 346.203 252.809 346.203 252.809C346.203 252.809 361.355 221.477 404.363 241.991C447.371 262.504 432.556 293.998 432.556 293.998Z" fill="white"/>
</g>
}
@ -157,14 +158,20 @@ export default class AgidaTheme extends Vue {
}
get yinYangSVG(): JSX.Element {
return <g>
return <g style="transform: translate(30px, 30px);">
<path fill-rule="evenodd" clip-rule="evenodd" d="M289.088 578.175C448.746 578.175 578.175 448.746 578.175 289.088C578.175 129.429 448.746 0 289.088 0C129.429 0 0 129.429 0 289.088C0 448.746 129.429 578.175 289.088 578.175ZM289.087 570.763C214.383 570.763 142.737 541.086 89.9132 488.262C37.0889 435.437 7.41248 363.792 7.41248 289.087C7.41248 214.383 37.0889 142.737 89.9132 89.9132C142.737 37.0889 214.383 7.41248 289.087 7.41248C326.44 7.41248 362.262 22.2507 388.675 48.6628C415.087 75.075 429.925 110.898 429.925 148.25C429.925 185.602 415.087 221.425 388.675 247.837C362.262 274.249 326.44 289.087 289.087 289.087C251.735 289.087 215.912 303.926 189.5 330.338C163.088 356.75 148.25 392.573 148.25 429.925C148.25 467.277 163.088 503.1 189.5 529.512C215.912 555.924 251.735 570.763 289.087 570.763ZM326.15 429.925C326.15 450.394 309.557 466.988 289.088 466.988C268.618 466.988 252.025 450.394 252.025 429.925C252.025 409.456 268.618 392.863 289.088 392.863C309.557 392.863 326.15 409.456 326.15 429.925Z" fill="white"/>
<path d="M289.088 185.312C309.557 185.312 326.15 168.719 326.15 148.25C326.15 127.781 309.557 111.188 289.088 111.188C268.618 111.188 252.025 127.781 252.025 148.25C252.025 168.719 268.618 185.312 289.088 185.312Z" fill="white"/>
</g>
}
get activeFragment() {
return this.agidaSVG
const objectMap: Record<AgidaTypes, JSX.Element> = {
agida: this.agidaSVG,
knife: this.knifeSVG,
'yin yang': this.yinYangSVG
}
return objectMap[this.type] || this.agidaSVG
}
get width(): number {
@ -207,7 +214,6 @@ export default class AgidaTheme extends Vue {
generateWave(type: 'bottom' | 'top') {
const wavery = new Wavery(this.wave)
const waveSvg = wavery.generateSvg()
console.log('GENERATE WAVE')
const { height, width, xmlns, paths } = waveSvg.svg
const isTop = type === 'top'

@ -1,4 +1,4 @@
import { AppInputThemeGeneral, AppTheme } from '@/models/app'
import { AppInputThemePalette, AppTheme } from '@/models/app'
import { AppModule } from '@/store/app'
import { Component, Vue } from 'vue-property-decorator'
import { CreateElement } from 'vue/types/umd'
@ -10,7 +10,7 @@ export default class InfinityTheme extends Vue {
}
get palette() {
const input = AppModule.getThemeInput('palette') as AppInputThemeGeneral
const input = AppModule.getThemeInput('palette') as AppInputThemePalette
const index = input?.value as number || 0
const values = input?.values || []

@ -75,6 +75,7 @@
"general": "General",
"choice-themes": "Choice themes",
"customize-theme": "Customize theme",
"change-theme": "Сhange theme",
"reset-settings": "Reset settings",
"login-position": {
"title": "Position",

@ -19,7 +19,8 @@
"invert": "Инвертировать",
"brightness": "Яркость",
"random": "Случайно",
"symmetry": "Симметрия"
"symmetry": "Симметрия",
"thickness": "Толщина"
},
"text": {
"password": "пароль",
@ -71,6 +72,7 @@
"choice-themes": "Выбор темы",
"customize-theme": "Настройка темы",
"reset-settings": "Сбросить настройки",
"change-theme": "Сменить тему",
"login-position": {
"title": "Положение",
"about": "Выбрать положение логина",

@ -25,8 +25,8 @@ export interface AppThemeSnapshot {
values: Record<string, AppInputThemeValue>;
}
export type AppInputTheme = AppInputThemeGeneral | AppInputThemeSlider | AppInputButton
export type AppInputThemeType = 'color' | 'slider' | 'checkbox' | 'palette' | 'button'
export type AppInputTheme = AppInputThemeGeneral | AppInputThemeSlider | AppInputButton | AppInputThemePalette | AppInputThemeSelector
export type AppInputThemeType = 'color' | 'slider' | 'checkbox' | 'palette' | 'button' | 'selector'
export type AppInputThemeValue = string | boolean | string[] | number
export interface AppInputThemeGeneral {
@ -34,7 +34,6 @@ export interface AppInputThemeGeneral {
value: AppInputThemeValue;
label: string;
type: AppInputThemeType;
values?: string[][];
options?: AppInputThemeOptions;
callback?: (value: AppInputThemeValue) => void;
}
@ -45,6 +44,16 @@ export interface AppInputThemeSlider extends AppInputThemeGeneral {
options: AppInputThemeOptionsSlider;
}
export interface AppInputThemeSelector extends AppInputThemeGeneral {
type: 'selector';
values: Readonly<string[]>;
}
export interface AppInputThemePalette extends AppInputThemeGeneral {
type: 'palette';
values: string[][];
}
export interface AppInputButton {
// TODO: "name" and "value" useless property, need to delete them
name: 'button';

@ -1,55 +1,56 @@
import { Greeter, Signal } from 'nody-greeter-types'
export interface Lightdm {
can_suspend: boolean;
can_shutdown: boolean;
can_restart: boolean;
can_hibernate: boolean;
is_authenticated: boolean;
authentication_user?: string;
default_session: string;
sessions: LightdmSession[];
users: LightdmUsers[];
languages: LightdmLanguage[];
language: string;
has_guest_account: boolean;
start_authentication(username: string): void;
authenticate(username: string): void;
cancel_authentication(): void;
respond(password: string): void;
login(user: string, session: string): void;
shutdown(): void;
hibernate(): void;
suspend(): void;
restart(): void;
}
export interface LightdmUsers {
display_name: string;
username: string;
image?: string;
display_name: string
username: string
image?: string
}
export interface LightdmLanguage {
name: string;
code: string;
name: string
code: string
}
export interface LightdmSession {
name: string;
key: string;
comment?: string;
name: string
key: string
comment?: string
}
export interface Lightdm {
can_suspend: boolean
can_shutdown: boolean
can_restart: boolean
can_hibernate: boolean
is_authenticated: boolean
authentication_user?: string
default_session: string
sessions: LightdmSession[]
users: LightdmUsers[]
languages: LightdmLanguage[]
language: string
has_guest_account: boolean
start_authentication(username: string): void
authenticate(username: string): void
cancel_authentication(): void
respond(password: string): void
login(user: string, session: string): void
shutdown(): void
hibernate(): void
suspend(): void
restart(): void
}
declare global {
interface Window {
authentication_complete(): void;
authentication_complete(): void
lightdmLogin(
username: string,
password: string,
callback: () => void,
): void;
show_prompt(text: string, type?: string): void;
show_message(text: string, type: any): void;
show_prompt(text: string, type?: string): void
show_message(text: string, type: any): void
lightdm_start(desktop: string): void;
lightdm_cancel_login(): void;
}
}

@ -22,14 +22,18 @@ import { isDifferentRoute, parseQueryValue, randomize, randomizeSettingsTheme }
import { AppThemes, defaultTheme } from '@/utils/constant'
import { version } from '@/../package.json'
import { LightdmHandler } from '@/utils/lightdm'
import { LightDMBattery } from 'nody-greeter-types'
export interface AppState extends AppSettings {
themes: AppTheme[];
getMainSettings: AppSettings;
activeTheme: AppTheme;
battery?: LightDMBattery;
brightness?: number;
username: string;
desktops: LightdmSession[];
users: LightdmUsers[];
SET_STATE_APP: <S extends this, K extends keyof this>({ key, value }: { key: K; value: S[K] }) => void
}
@Module({ dynamic: true, store, name: 'app' })
@ -38,9 +42,11 @@ class App extends VuexModule implements AppState {
currentTheme = ''
currentOs = 'arch-linux'
desktop = LightdmHandler.defaultSession
username = LightdmHandler?.username
username = LightdmHandler.username
password = ''
defaultColor = '#6BBBED'
battery?: LightDMBattery = undefined
brightness = 0
users = LightdmHandler?.users
desktops = LightdmHandler?.sessions
@ -55,8 +61,16 @@ class App extends VuexModule implements AppState {
'only-ui': false
}
get isAdvancedGreeted() {
return LightdmHandler.isNode
get isCharging() {
return this.battery?.status === 'Charging'
}
get batteryLevel() {
return this.battery?.level || 0
}
get isSupportFullApi() {
return LightdmHandler.isSupportFullApi
}
// TODO: replace this on localStorageSettings

@ -0,0 +1,64 @@
.app-bar
--icon-size: 24px
position absolute
top 0
left 0
width 100%
font-size 0.875rem
background var(--background-block)
display flex
justify-content center
height var(--height-bar)
align-items center
padding 0 var(--gap)
.app-bar__info
display flex
gap var(--gap)
position absolute
right var(--gap)
.app-bar-battery
display flex
align-items center
.app-bar-battery-icon
position relative
width var(--icon-size)
height calc(var(--icon-size) / 2)
border calc(var(--icon-size) / 40) white solid
border-radius calc(var(--icon-size) / 9)
margin-right calc(var(--gap) / 2)
&::before
content ''
display block
top 50%
right 0px
background white
position absolute
border-radius 0 calc(var(--icon-size) / 30) calc(var(--icon-size) / 30) 0
width calc(var(--icon-size) / 10)
height calc(var(--icon-size) / 5)
transform translate(100%, -50%)
.app-bar-battery-icon__fill
max-width 100%
height 100%
background var(--color-unfocus)
&.charging
background var(--color-green)
.app-bar-battery-icon__charge
width 80%
position absolute
top 50%
left 50%
transform translate(-50%, -50%)
.app-bar__bright
gap calc(var(--gap) / 2)
display flex
align-items center
.brightness-icon
width 1.1rem

@ -1,3 +1,4 @@
@import './bar.styl'
@import './menu.styl'
@import './login.styl'
@import './dialog.styl'
@ -20,12 +21,12 @@
justify-content center
.frame-rate-block
top 0
right 0
top calc(var(--gap) + var(--height-bar))
right var(--gap)
color white
font-size 1.1rem
padding 6px 12px
padding calc(var(--gap) / 2) var(--gap)
z-index 10
position absolute
background var(--background-block)
border-radius 0 0 0 10px
border-radius var(--gap)

@ -19,7 +19,7 @@
&.login-view--center
transform translate(calc(100vw / 2 - 50%), calc(100vh / 2 - 50%))
&.login-view--top
transform translate(calc(100vw / 2 - 50%), 12px)
transform translate(calc(100vw / 2 - 50%), calc(var(--gap) + var(--height-bar)))
&.login-view--right
transform translate(calc(100vw - 100% - 12px), calc(100vh / 2 - 50%))
&.login-view--bottom
@ -58,12 +58,10 @@
width 60px
height 60px
margin 0
.time
position absolute
transform translate(74px, 32px)
.user-name
margin 8px 12px
display flex
align-items center
.user-choice
width 100%
@ -72,6 +70,10 @@
height 50px
.time
position absolute
top 0
left 50%
transform translateX(-50%)
text-align center
color var(--color-unfocus)
font-size 0.95rem
@ -82,7 +84,7 @@
height 100px
display block
transition .3s
margin auto
margin 24px auto 0
background-repeat no-repeat
background-size cover
background-position center

@ -1,6 +1,7 @@
.selector
padding 4px 8px
border-radius 4px
max-height 36px
border 1px solid var(--color-unfocus)
color var(--color-unfocus)
display flex

@ -86,7 +86,7 @@
left auto
right 0
height 0
width 10%
width 100%
.app-slider__content
white-space nowrap

@ -12,8 +12,9 @@
--color-blue #04ded4
--color-unfocus rgba(255, 255, 255, .5)
--color-focus white
--login-height: 14vmin
--background-block: rgba(0,0,0,0.45)
--gap: 12px
--height-bar: 36px
font-size 16px
::-webkit-scrollbar
@ -37,3 +38,19 @@
animation-iteration-count 1 !important
transition-duration 0.01ms !important
scroll-behavior auto !important
// @media screen and (-moz-min-device-pixel-ratio: 2),
// screen and (-o-min-device-pixel-ratio: 2/1),
// screen and (-webkit-min-device-pixel-ratio: 2),
// screen and (min-device-pixel-ratio: 2)
// body
// zoom 2
// @media screen and (-webkit-min-device-pixel-ratio: 2)
// body
// zoom 2
@media screen and (min-width: 3000px) and (min-height: 1200px)
body
zoom 2

@ -86,10 +86,6 @@ img:not([alt])
transition-duration 0.01ms !important
scroll-behavior auto !important
@media (resolution >= 2dppx)
body
zoom 2
section
position relative
box-sizing border-box

@ -31,6 +31,9 @@ export const defaultTheme: AppTheme = {
]
}
const AGIDA_TYPES = ['agida', 'knife', 'yin yang'] as const
export type AgidaTypes = typeof AGIDA_TYPES[number]
export const AppThemes: AppTheme[] = [
{
name: 'Agida',
@ -55,6 +58,13 @@ export const AppThemes: AppTheme[] = [
['#fc5185', '#3fc1c9'],
['#f5f5f5', '#364f6b']
]
},
{
name: 'type',
label: 'type',
type: 'selector',
value: 'agida',
values: AGIDA_TYPES
}
]
},

@ -1,4 +1,4 @@
import { AppInputButton, AppInputThemeGeneral, AppInputThemeSlider, AppInputThemeValue, AppTheme } from '@/models/app'
import { AppInputButton, AppInputThemeGeneral, AppInputThemePalette, AppInputThemeSlider, AppInputThemeValue, AppTheme } from '@/models/app'
import { AppModule } from '@/store/app'
import { PageModule } from '@/store/page'
import { debounce, DebounceSettings } from 'lodash'
@ -6,7 +6,6 @@ import { RawLocation } from 'vue-router'
import router from '../router'
import { LightdmHandler } from '@/utils/lightdm'
const isFinalBuild = process.env.VUE_APP_VIEW === 'build'
export const modKey = 'ctrl'
export const languageMap: Record<string, string> = {
ru: 'Русский',
@ -162,18 +161,18 @@ export function hasSomeParentClass(element: HTMLElement, tag: string): boolean {
}
export function randomizeSettingsTheme(theme: AppTheme) {
const generateValueObject: Record<string, (input: AppInputThemeSlider) => AppInputThemeValue> = {
const generateValueObject: Record<string, (input: any) => any> = {
slider: (input: AppInputThemeSlider) => generateRandomSliderValue(input),
checkbox: () => Math.random() > 0.5,
color: () => generateRandomColor(),
palette: (input: AppInputThemeGeneral) => Math.floor(randomize(0, (input.values?.length || 2) - 1))
palette: (input: AppInputThemePalette) => Math.floor(randomize(0, (input.values?.length || 2) - 1))
}
return theme.settings?.map(input => {
const changeValueFunction = generateValueObject[input.type]
if (changeValueFunction) {
input.value = changeValueFunction(input as AppInputThemeSlider)
input.value = changeValueFunction(input)
}
return input

@ -1,19 +1,68 @@
import { Lightdm } from '@/models/lightdm'
import { Greeter } from 'nody-greeter-types'
import { AppState } from '@/store/app'
/**
* INFO: To avoid recoursive requires modules (lightdm and AppModule)
* @returns AppState
*/
async function getAppModule(): Promise<AppState> {
const module = await import('@/store/app') as any
return module.AppModule
}
const DEBUG_PASSWORD = 'password'
const lightdmDebug = window.lightdm === undefined
const localLight = window.lightdm as unknown as Lightdm
function setIsAuthenticated(value: boolean) {
(window.lightdm as any).is_authenticated = value
}
if (lightdmDebug) {
window.lightdm = {
is_authenticated: false,
authentication_user: undefined,
default_session: 'plasma-shell',
can_access_battery: true,
can_access_brightness: true,
can_suspend: true,
can_restart: true,
can_hibernate: true,
can_shutdown: true,
battery_data: {
level: Math.ceil(Math.random() * 99 + 1),
ac_status: true
},
brightness: Math.ceil(Math.random() * 99 + 1),
battery_update: {
_callbacks: [],
_emit: () => {
window.lightdm?.battery_update._callbacks.forEach((cb) => cb())
},
connect: (callback: () => void) => {
window.lightdm?.battery_update._callbacks.push(callback)
}
},
authentication_complete: {
_callbacks: [],
_emit: () => {
console.log(window.lightdm?.authentication_complete._callbacks)
window.lightdm?.authentication_complete._callbacks.forEach((cb) => cb())
},
connect: (callback: () => void) => {
window.lightdm?.authentication_complete._callbacks.push(callback)
}
},
brightness_update: {
_callbacks: [],
_emit: () => {
window.lightdm?.brightness_update._callbacks.forEach((cb) => cb())
},
connect: (callback: () => void) => {
window.lightdm?.brightness_update._callbacks.push(callback)
}
},
sessions: [
{
name: 'i3wm',
@ -60,39 +109,48 @@ if (lightdmDebug) {
}
],
languages: [
{
name: 'American English',
code: 'en_US.utf8'
},
{
name: 'Русский',
code: 'ru_RU.utf8'
},
{
name: 'American English',
code: 'en_US.utf8'
}
],
language: 'American English',
language: { code: 'en_US', name: 'American English' },
start_authentication: (username: string) => {
console.log(`Starting authenticating here: '${username}'`)
const inputNode = document.getElementById('password') as HTMLInputElement
localLight.respond(inputNode?.value || '')
window.lightdm?.respond(inputNode?.value || '')
},
authenticate: (username: string) => {
const inputNode = document.getElementById('password') as HTMLInputElement
console.log(`Starting authenticating user: '${username}'`)
if (window.lightdm) {
(window.lightdm as any).authentication_user = username
}
window.lightdm?.respond(inputNode?.value || '')
},
cancel_authentication: () => {
console.log('Auth cancelled')
},
start_session(session: string) {
alert(`Start session: ${session}`)
},
respond: (password: string) => {
console.log(`Password provided : '${password}'`)
if (password === DEBUG_PASSWORD) {
localLight.is_authenticated = true
setIsAuthenticated(true)
} else {
setIsAuthenticated(false)
}
window.authentication_complete()
},
login(user: string, session: string) {
alert(`Logged with '${user}' (Session: '${session}') !`)
window.lightdm?.authentication_complete._emit()
},
shutdown() {
alert('(DEBUG: System is shutting down)')
@ -109,21 +167,17 @@ if (lightdmDebug) {
} as any
}
const isNode = 'batteryData' in (window.lightdm || {})
const isSupportFullApi = 'battery_data' in (window.lightdm || {})
class LightdmWebkit {
protected _inputErrorTimer!: null | NodeJS.Timeout
// get session() {
// return ''
// }
get defaultSession() {
return this.sessions[0]?.key || window.lightdm?.default_session || 'i3'
}
get isNode() {
return isNode
get isSupportFullApi() {
return isSupportFullApi
}
get sessions() {
@ -195,96 +249,50 @@ class LightdmWebkit {
}
}
class LightdmPython extends LightdmWebkit {
private _password = ''
private _completeCallback!: () => void
constructor() {
super()
this.init()
}
get light() {
return window.lightdm as unknown as Lightdm
}
public login(username: string, password: string, session?: string): void {
this.lightdmLogin(username, password, () => this.lightdmStart(session || this.defaultSession))
}
private lightdmStart(session: string): void {
this.light.login(this.light.authentication_user || '', session)
}
private lightdmLogin(username: string, password: string, callback: () => void) {
this._completeCallback = callback
this._password = password
this.light.start_authentication(username)
}
private init() {
window.authentication_complete = () => {
if (window.lightdm?.is_authenticated && this._completeCallback) {
this._completeCallback()
} else {
this.light.cancel_authentication()
this._setInputError()
}
}
window.show_message = (text) => {
alert(text)
}
window.show_prompt = (text) => {
if (text === 'Password: ') {
if (window.lightdm) {
window.lightdm.respond(this._password)
}
}
}
}
}
class LightdmNode extends LightdmWebkit {
private _username!: string
private _password!: string
private _session!: undefined | string
public _username!: string
public _password!: string
public _session!: string
constructor() {
super()
this.init()
}
get light() {
return window.lightdm as Greeter
}
public login(username: string, password: string, session?: string): void {
this._username = username
this._password = password
this._session = session
this._session = session || this.defaultSession
this.light.authenticate(null)
window.lightdm?.authenticate(username)
}
public setAuthenticationDone(): void {
this.light.authentication_complete.connect(() => {
if (this.light.is_authenticated) {
this.light.start_session(this._session || this.light.default_session)
window.lightdm?.authentication_complete?.connect(() => {
if (window.lightdm?.is_authenticated) {
window.lightdm?.start_session(this._session || window.lightdm?.default_session)
} else {
this._authenticationFailed()
}
})
window.lightdm_cancel_login = () => {
window.lightdm?.cancel_authentication()
}
}
public _authenticationFailed(): void {
this.light.cancel_authentication()
private _authenticationFailed(): void {
this._setInputError()
window.lightdm?.cancel_authentication()
}
public setSignalHandler(): void {
this.light.show_prompt.connect((_message, type) => {
window.lightdm?.show_message?.connect(function(text, type) {
console.log({ text, type })
})
window.lightdm?.show_prompt?.connect((_message, type) => {
console.log({ _message, type })
if (!window.lightdm) return
if (type === 0) {
window.lightdm.respond(this._username)
@ -292,6 +300,26 @@ class LightdmNode extends LightdmWebkit {
window.lightdm.respond(this._password)
}
})
if (window.lightdm?.can_access_brightness) {
this.updateBrightData()
window.lightdm?.brightness_update.connect(this.updateBrightData)
}
if (window.lightdm?.can_access_battery) {
this.updateBatteryData()
window.lightdm?.battery_update.connect(this.updateBatteryData)
}
}
public async updateBatteryData(): Promise<void> {
const module = await getAppModule()
module.SET_STATE_APP({ key: 'battery', value: window.lightdm?.battery_data })
}
public async updateBrightData(): Promise<void> {
const module = await getAppModule()
module.SET_STATE_APP({ key: 'brightness', value: window.lightdm?.brightness })
}
public init(): void {
@ -300,4 +328,26 @@ class LightdmNode extends LightdmWebkit {
}
}
export const LightdmHandler = isNode ? new LightdmNode() : new LightdmPython()
export const LightdmHandler = new LightdmNode()
window.lightdm_cancel_login = () => {
window.lightdm?.cancel_authentication()
}
window.lightdm_start = (desktop: string) => {
window.lightdm?.start_session(desktop)
}
window.show_prompt = (text, type) => {
if (text === 'Password: ' && LightdmHandler._password !== undefined) {
window.lightdm?.respond(LightdmHandler._password)
}
}
window.authentication_complete = () => {
if (window.lightdm?.is_authenticated) {
window.lightdm_start(LightdmHandler._session)
} else if (document.head.dataset.wintype === 'primary') {
window.lightdm?.cancel_authentication()
}
}

@ -0,0 +1,41 @@
import { PageModule } from '@/store/page'
import Vue from 'vue'
import { DateTimeFormatOptions } from 'vue-i18n'
const timeRef = Vue.observable({
time: new Date(),
shortTime: '',
longTime: ''
})
const formatTime = (type: 'long' | 'short' = 'short') => {
const options: DateTimeFormatOptions = {
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: false
}
if (type === 'long') {
options.month = 'long'
options.weekday = 'long'
}
return new Intl.DateTimeFormat(PageModule.locale, options).format(new Date())
}
export const initTimer = () => {
setInterval(updateTime, 1000)
}
const updateTime = () => {
timeRef.time = new Date()
timeRef.shortTime = formatTime('short')
timeRef.longTime = formatTime('long')
}
updateTime()
export default timeRef

@ -4,6 +4,7 @@ import { LoginPosition } from '@/models/page'
import { AppModule } from '@/store/app'
import { PageModule } from '@/store/page'
import AppBar from '@/components/app/AppBar'
import AppMenu from '@/components/app/AppMenu'
import AppDialog from '@/components/app/AppDialog'
import SettingsComponent from '@/components/base/SettingsComponent'
@ -66,6 +67,10 @@ export default class HomePage extends Vue {
return !this.isViewThemeOnly && this.isOpenLogin
}
get isSupportFullApi() {
return AppModule.isSupportFullApi
}
created() {
// Set language
const language = localStorage.getItem('language') || 'en'
@ -112,6 +117,7 @@ export default class HomePage extends Vue {
{ !this.isViewThemeOnly && <ShutdownButton /> }
{ this.showGithubButton && <GithubButton /> }
{ this.isSupportFullApi && <AppBar /> }
<AppDialog />
<AppMenu />

@ -1,5 +1,6 @@
#!/bin/bash
dm-tool add-nested-seat --screen 1366x768
lightdm-webkit2-greeter
# dm-tool add-nested-seat --screen 1366x768
# nody-greeter --debug
# yarn build && sudo sh ./install.sh && nody-greeter --debug

Loading…
Cancel
Save