diff --git a/Makefile b/Makefile index b8af3d14..24b18e83 100644 --- a/Makefile +++ b/Makefile @@ -146,6 +146,7 @@ pug: testenv -it $(TESTENV_IMAGE) bash -c "cd src \ && pug --pretty web/index.pug -o web \ && pug --pretty web/login/index.pug -o web/login \ + && pug --pretty web/kvm/index.pug -o web/kvm \ && pug --pretty web/ipmi/index.pug -o web/ipmi \ && pug --pretty web/vnc/index.pug -o web/vnc \ " diff --git a/web/base.pug b/web/base.pug index 98e94a67..2113e9aa 100644 --- a/web/base.pug +++ b/web/base.pug @@ -25,6 +25,7 @@ doctype html - var css_dir = "/share/css" - var js_dir = "/share/js" - var svg_dir = "/share/svg" +- var png_dir = "/share/png" - var title = "" - var main_js = "" diff --git a/web/kvm/footer.pug b/web/kvm/footer.pug new file mode 100644 index 00000000..53f196ef --- /dev/null +++ b/web/kvm/footer.pug @@ -0,0 +1,4 @@ +ul(class="footer") + li(id="kvmd-meta-server-host" class="footer-left") + li(class="footer-right") + a(target="_blank" href="https://pikvm.org") Pi-KVM Project diff --git a/web/kvm/index.html b/web/kvm/index.html index b74262d6..7ddd7659 100644 --- a/web/kvm/index.html +++ b/web/kvm/index.html @@ -1,5 +1,6 @@ - - +============================================================================== +--> - - - Pi-KVM Session - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
Stream
-
-
-
- -
-
-
-
-
Mouse
Left
-

← Hold
-
-
Mouse
Middle
-

← Hold
-
-

Hold →
-
Mouse
Right
-
-
-
-
- -
-
-
Virtual Keyboard
- -
-
-
-
-
Esc
-
-
F1
-
F2
-
F3
-
F4
-
-
F5
-
F6
-
F7
-
F8
-
-
F9
-
F10
-
F11
-
F12
-
-
-
-
~
`
-
!
1
-
@
2
-
#
3
-
$
4
-
%
5
-
^
6
-
&
7
-
*
8
-
(
9
-
)
0
-
_
-
-
+
=
-
-
-
-

-
Q
-
W
-
E
-
R
-
T
-
Y
-
U
-
I
-
O
-
P
-
{
[
-
}
]
-
|
\
-
-
-
-
- -
- Caps Lock -
-
-
A
-
S
-
D
-
F
-
G
-
H
-
J
-
K
-
L
-
:
;
-
"
'
-
Enter
-
-
-

Shift
-
Z
-
X
-
C
-
V
-
B
-
N
-
M
-
<
,
-
>
.
-
?
/
-

Shift
-
-
-

Ctrl
-

Win
-

Alt
-
-

Alt
-

Win
-

Ctrl
-
-
-
-
-

Pt/Sq
-
-
- -
- ScrLk -
-
-
P/Brk
-
-
-
-
Ins
-
Home
-
PgUp
-
-
-
Del
-
End
-
PgDn
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
Esc
-
-
F1
-
F2
-
F3
-
F4
-
F5
-
F6
-
F7
-
F8
-
F9
-
F10
-
F11
-
F12
-
-

Pt/Sq
-
-
- -
- ScrLk -
-
-
P/Brk
-
Ins
-
Home
-
End
-
Del
-
-
-
-
~
`
-
!
1
-
@
2
-
#
3
-
$
4
-
%
5
-
^
6
-
&
7
-
*
8
-
(
9
-
)
0
-
_
-
-
+
=
-
-
-
-

-
Q
-
W
-
E
-
R
-
T
-
Y
-
U
-
I
-
O
-
P
-
{
[
-
}
]
-
|
\
-
-
-
-
- -
- Caps Lock -
-
-
A
-
S
-
D
-
F
-
G
-
H
-
J
-
K
-
L
-
:
;
-
"
'
-
Enter
-
-
-

Shift
-
Z
-
X
-
C
-
V
-
B
-
N
-
M
-
<
,
-
>
.
-
?
/
-
PgUp
-
-
PgDn
-
-
-

Ctrl
-

Win
-

Alt
-
-

Alt
-

Win
-

Shift
-

Ctrl
-
-
-
-
-
-
-
- -
-
-
About
- -
-
- - - - - -
- - - - - - - -
Open Source & Open Hardware IP-KVM
-
-
-
- - - - - - - - - - - - -
-
- No data -
-
- -
-
- No data -
-
- -
-
- No data -
-
- -
-
- - // These kind people donated money to the Pi-KVM project
- // and supported the work on it. We are very grateful
- // for their help, and memorializing their names
- // is the least we can do in gratitude.
- // If you also want to support this project,
- // you can use one of these services: - Patreon - or PayPal. -
-
    -
  • Aleksei Brusianskii
  • -
  • Arthur Woimbée
  • -
  • Ben Gordon
  • -
  • Branden Shaulis
  • -
  • Christof Maluck
  • -
  • Corey Lista
  • -
  • David Howell
  • -
  • Denis Yatsenko
  • -
  • Ge Men
  • -
  • Grey Cynic
  • -
  • Jason Toland
  • -
  • Jeff Bowman
  • -
  • John McGovern
  • -
  • Mark Gilbert
  • -
  • Mark Robinson
  • -
  • Mauricio Allende
  • -
  • Michael Lynch
  • -
  • Samed Ozoglu
  • -
  • Truman Kilen
  • -
  • Walter_Ego
  • -
-
-
-
-
-

- Full documentation, source code, hardware schematics and legal information - can be found in our official website. -

-
-
- - - - + + + Pi-KVM Session + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
Stream
+
+
+
+
+
+
+
Mouse
Left
+

← Hold
+
+
Mouse
Middle
+

← Hold
+
+

Hold →
+
Mouse
Right
+
+
+
+
+
+
+
Virtual Keyboard
+ +
+
+
+
+
+
Esc +
+
+
+
+
F1 +
+
+
+
F2 +
+
+
+
F3 +
+
+
+
F4 +
+
+
+
+
F5 +
+
+
+
F6 +
+
+
+
F7 +
+
+
+
F8 +
+
+
+
+
F9 +
+
+
+
F10 +
+
+
+
F11 +
+
+
+
F12 +
+
+
+
+
+
+
~
` +
+
+
+
!
1 +
+
+
+
@
2 +
+
+
+
#
3 +
+
+
+
$
4 +
+
+
+
%
5 +
+
+
+
^
6 +
+
+
+
&
7 +
+
+
+
*
8 +
+
+
+
(
9 +
+
+
+
)
0 +
+
+
+
_
- +
+
+
+
+
= +
+
+
+
↤ +
+
+
+
+
+

⇥ +
+
+
+
Q +
+
+
+
W +
+
+
+
E +
+
+
+
R +
+
+
+
T +
+
+
+
Y +
+
+
+
U +
+
+
+
I +
+
+
+
O +
+
+
+
P +
+
+
+
{
[ +
+
+
+
}
] +
+
+
+
|
\ +
+
+
+
+
+

Caps Lock +
+
+
+
A +
+
+
+
S +
+
+
+
D +
+
+
+
F +
+
+
+
G +
+
+
+
H +
+
+
+
J +
+
+
+
K +
+
+
+
L +
+
+
+
:
; +
+
+
+
"
' +
+
+
+
Enter
↵ +
+
+
+
+
+

Shift +
+
+
+
Z +
+
+
+
X +
+
+
+
C +
+
+
+
V +
+
+
+
B +
+
+
+
N +
+
+
+
M +
+
+
+
<
, +
+
+
+
>
. +
+
+
+
?
/ +
+
+
+

Shift +
+
+
+
+
+

Ctrl +
+
+
+

Win +
+
+
+

Alt +
+
+
+
+
+
+
+

Alt +
+
+
+

Win +
+
+
+

Ctrl +
+
+
+
+
+
+
+

Pt/Sq +
+
+
+

ScrLk +
+
+
+
P/Brk +
+
+
+
+
+
+
Ins +
+
+
+
Home +
+
+
+
PgUp +
+
+
+
+
+
Del +
+
+
+
End +
+
+
+
PgDn +
+
+
+
+
+
+
+
↑ +
+
+
+
+
+
+
← +
+
+
+
↓ +
+
+
+
→ +
+
+
+
+
+
+
+
+
+
Esc +
+
+
+
+
F1 +
+
+
+
F2 +
+
+
+
F3 +
+
+
+
F4 +
+
+
+
F5 +
+
+
+
F6 +
+
+
+
F7 +
+
+
+
F8 +
+
+
+
F9 +
+
+
+
F10 +
+
+
+
F11 +
+
+
+
F12 +
+
+
+
+

Pt/Sq +
+
+
+

ScrLk +
+
+
+
P/Brk +
+
+
+
Ins +
+
+
+
Home +
+
+
+
End +
+
+
+
Del +
+
+
+
+
+
~
` +
+
+
+
!
1 +
+
+
+
@
2 +
+
+
+
#
3 +
+
+
+
$
4 +
+
+
+
%
5 +
+
+
+
^
6 +
+
+
+
&
7 +
+
+
+
*
8 +
+
+
+
(
9 +
+
+
+
)
0 +
+
+
+
_
- +
+
+
+
+
= +
+
+
+
↤ +
+
+
+
+
+

⇥ +
+
+
+
Q +
+
+
+
W +
+
+
+
E +
+
+
+
R +
+
+
+
T +
+
+
+
Y +
+
+
+
U +
+
+
+
I +
+
+
+
O +
+
+
+
P +
+
+
+
{
[ +
+
+
+
}
] +
+
+
+
|
\ +
+
+
+
+
+

Caps Lock +
+
+
+
A +
+
+
+
S +
+
+
+
D +
+
+
+
F +
+
+
+
G +
+
+
+
H +
+
+
+
J +
+
+
+
K +
+
+
+
L +
+
+
+
:
; +
+
+
+
`
' +
+
+
+
Enter
↵ +
+
+
+
+
+

Shift +
+
+
+
Z +
+
+
+
X +
+
+
+
C +
+
+
+
V +
+
+
+
B +
+
+
+
N +
+
+
+
M +
+
+
+
lt;
, +
+
+
+
>
. +
+
+
+
?
/ +
+
+
+
PgUp +
+
+
+
↑ +
+
+
+
PgDn +
+
+
+
+
+

Ctrl +
+
+
+

Win +
+
+
+

Alt +
+
+
+
+
+
+
+

Alt +
+
+
+

Win +
+
+
+

Shift +
+
+
+

Ctrl +
+
+
+
← +
+
+
+
↓ +
+
+
+
→ +
+
+
+
+
+
+
+
+
About
+ +
+
+ + + + + +
+ + + + + + + +
Open Source & Open Hardware IP-KVM
+

+
+ + + + + + + + +
+
No data
+
+
+
No data
+
+
+
No data
+
+
+
// These kind people donated money to the Pi-KVM project
+ // and supported the work on it. We are very grateful
+ // for their help, and memorializing their names
+ // is the least we can do in gratitude.
+ // If you also want to support this project,
+ // you can use one of these services: + Patreon + or PayPal.
+
    +
  • Aleksei Brusianskii
  • +
  • Arthur Woimbée
  • +
  • Ben Gordon
  • +
  • Branden Shaulis
  • +
  • Christof Maluck
  • +
  • Corey Lista
  • +
  • David Howell
  • +
  • Denis Yatsenko
  • +
  • Ge Men
  • +
  • Grey Cynic
  • +
  • Jason Toland
  • +
  • Jeff Bowman
  • +
  • John McGovern
  • +
  • Mark Gilbert
  • +
  • Mark Robinson
  • +
  • Mauricio Allende
  • +
  • Michael Lynch
  • +
  • Samed Ozoglu
  • +
  • Truman Kilen
  • +
  • Walter_Ego
  • +
+
+
+

+

+ Full documentation, source code, hardware schematics and legal information + can be found in our official website. +

+
+
+ + + \ No newline at end of file diff --git a/web/kvm/index.pug b/web/kvm/index.pug new file mode 100644 index 00000000..c39ef7b6 --- /dev/null +++ b/web/kvm/index.pug @@ -0,0 +1,13 @@ +extends ../base.pug + +append vars + - title = "Pi-KVM Session" + - main_js = "kvm/main" + - body_class = "body-no-select" + - css_list = css_list.concat(["navbar", "window", "modal", "led", "slider", "switch", "progress", "keypad", "tabs"]) + - css_list = css_list.concat(["kvm/stream", "kvm/hid", "kvm/msd", "kvm/keyboard", "kvm/about"]) + +block body + include navbar.pug + include windows.pug + include footer.pug diff --git a/web/kvm/navbar.pug b/web/kvm/navbar.pug new file mode 100644 index 00000000..d7b5c0cc --- /dev/null +++ b/web/kvm/navbar.pug @@ -0,0 +1,263 @@ +mixin navbar_led(id, icon) + img(data-dont-hide-menu id=id, class="led-gray" src=`${svg_dir}/${icon}.svg`) + +mixin navbar_message(icon, short) + div(class="text") + table + tr + td #[img(class="sign" src=`${svg_dir}/${icon}.svg`)] + td + | #[b #{short}] + if block + br + sup + block + +mixin navbar_health_message(icon, short, gray) + div(class="text") + table + tr + td #[img(class=`sign ${gray ? " led-gray" : ""}` src=`${svg_dir}/${icon}.svg`)] + td + | #[b #{short}] #[br] #[br] + sup + block + +mixin switch(id) + div(class="switch-box") + input(checked type="checkbox" id=id class="switch-checkbox") + label(class="switch-label" for=id) + span(class="switch-inner") + span(class="switch") + +ul(id="navbar") + li(class="left") + a(id="logo" href="/") ←   + img(class="svg-gray" src=`${svg_dir}/logo.svg` alt="π-kvm") + + div(id="hw-health-dropdown" class="hidden") + li(class="left") + a(class="menu-button" href="#") + img(data-dont-hide-menu id="hw-health-undervoltage-led" class="hidden" src=`${svg_dir}/led-undervoltage.svg`) + img(data-dont-hide-menu id="hw-health-overheating-led" class="hidden" src=`${svg_dir}/led-overheating.svg`) + | ↴ + div(data-dont-hide-menu class="menu") + +navbar_health_message("warning", "Raspberry Pi's health is at risk", false) + | This is not a drill! A red icon indicates a current issue,#[br] + | a yellow one that was observed since the device booted up. + div(id="hw-health-message-undervoltage" class="hidden") + hr + +navbar_health_message("led-undervoltage", "Undervoltage detected", true) + | Make sure your power supply and cabling are providing#[br] + | enough power to the Raspberry Pi (3A minimum). + div(id="hw-health-message-overheating" class="hidden") + hr + +navbar_health_message("led-overheating", "Overheating detected", true) + | Frequency capping due to overheating.#[br] + | Improve cooling of the Raspberry Pi. + + li(class="right") + a(class="menu-button" href="#") + +navbar_led("link-led", "led-link") + +navbar_led("stream-led", "led-stream") + +navbar_led("hid-keyboard-led", "led-hid-keyboard") + +navbar_led("hid-mouse-led", "led-hid-mouse") + | System ↴ + div(data-dont-hide-menu class="menu") + div(class="buttons") + button(disabled data-force-hide-menu id="stream-screenshot-button") • Take a screenshot + hr + button(data-force-hide-menu id="show-stream-button") • Show stream + button(data-force-hide-menu id="show-keyboard-button") • Show keyboard + button(data-force-hide-menu id="show-about-button") • Show about + div(id="stream-resolution" class="feature-disabled") + hr + div(class="text") + | Stream resolution: + div(class="stream-param-box") + select(disabled data-dont-hide-menu id="stream-resolution-selector") + div(id="stream-quality" class="feature-disabled") + hr + div(class="text") + | Stream quality: #[span(id="stream-quality-value") 80%] + div(class="stream-param-box") + input(disabled type="range" id="stream-quality-slider" class="slider") + hr + div(class="text") + | Stream FPS: #[span(id="stream-desired-fps-value") 0] + div(class="stream-param-box") + input(disabled type="range" id="stream-desired-fps-slider" class="slider") + hr + div(class="text") + | Stream size: #[span(id="stream-size-value") 100%] + div(class="stream-param-box") + input(type="range" id="stream-size-slider" class="slider") + hr + div(class="text") + table(class="one-line-switch") + td Auto-resize stream: + td(align="right") + +switch("stream-auto-resize-checkbox") + hr + div(class="buttons") + button(disabled data-force-hide-menu id="stream-reset-button") • Reset stream + button(disabled data-force-hide-menu id="hid-reset-button") • Reset keyboard & mouse + button(disabled data-force-hide-menu id="msd-reset-button" class="feature-disabled") • Reset mass storage + hr + div(class="buttons") + button(data-force-hide-menu id="open-log-button") • Open log + div(id="wol" class="buttons feature-disabled") + hr + button(disabled id="wol-wakeup-button") • Wake on LAN server + + li(id="atx-dropdown" class="right feature-disabled") + a(class="menu-button" href="#") + +navbar_led("atx-power-led", "led-atx-power") + +navbar_led("atx-hdd-led", "led-atx-hdd") + | ATX ↴ + div(class="menu") + div(class="buttons") + button(disabled id="atx-power-button") • Click Power #[sup #[i short]] + button(disabled id="atx-power-button-long") • Click Power #[sup #[i long]] + hr + button(disabled id="atx-reset-button") • Click Reset + + li(id="msd-dropdown" class="right feature-disabled") + a(class="menu-button" href="#") + +navbar_led("msd-led", "led-msd") + | Mass Storage ↴ + div(data-dont-hide-menu id="msd-menu" class="menu") + div(id="msd-message-offline" class="hidden") + +navbar_message("warning", "Mass Storage Device is offline") + hr + div(id="msd-message-image-broken" class="hidden") + +navbar_message("warning", "Current image is broken!") + | Perhaps uploading was interrupted + hr + div(id="msd-message-too-big-for-cdrom" class="hidden") + +navbar_message("warning", "Current image is too big for CD-ROM!") + | The device filesystem will be truncated to 2.2GiB + hr + div(id="msd-message-out-of-storage" class="hidden") + +navbar_message("warning", "Current image is out of storag") + | This image was connected manually using #[b kvmd-otgmsd] + hr + div(id="msd-message-another-user-uploads" class="hidden") + +navbar_message("info", "Another user uploads an image") + hr + table(class="kv") + tr + td Status: + td(id="msd-status" class="value") + hr + table(class="kv msd-single-storage feature-disabled") + tr + td Current image: + td(id="msd-image-name" class="value") + tr + td Image size: + td(id="msd-image-size" class="value") + tr + td Storage size: + td(id="msd-storage-size" class="value") + table(class="kv msd-multi-storage feature-disabled") + tr + td Image: + td(width="100%") #[select(disabled id="msd-image-selector")] + td #[button(disabled id="msd-remove-image") Remove] + table(class="kv msd-multi-storage feature-disabled") + tr(class="msd-cdrom-emulation feature-disabled") + td Emulate CD-ROM drive: + td + +switch("msd-emulate-cdrom-checkbox") + div(class="msd-multi-storage feature-disabled") + hr + div(class="text") + div(id="msd-storage-progress" class="progress") + span(id="msd-storage-progress-value" class="progress-value") + hr + input(type="file" id="msd-select-new-image-file" class="hidden") + div(class="buttons buttons-row") + button(disabled id="msd-select-new-image-button" class="row50") Upload new image + button(disabled id="msd-upload-new-image-button" class="row25") Start + button(disabled id="msd-abort-uploading-button" class="row25") Abort + hr + div(id="msd-submenu-new-image" class="hidden") + table(class="kv") + tr + td New image: + td(id="msd-new-image-name" class="value") + tr + td Upload size: + td(id="msd-new-image-size" class="value") + hr + div(class="text") + div(id="msd-uploading-progress" class="progress") + span(id="msd-uploading-progress-value" class="progress-value") + hr + div(class="buttons buttons-row") + button(disabled data-force-hide-menu id="msd-connect-button" class="row50") • Connect drive to Server + button(disabled data-force-hide-menu id="msd-disconnect-button" class="row50") • Disconnect drive + + li(class="right") + a(class="menu-button" href="#") + +navbar_led("hid-recorder-led", "led-gear") + | Macro ↴ + div(data-dont-hide-menu class="menu") + div(class="text") + b Record and play keyboard & mouse actions#[br] + sub For security reasons, the record will not saved on Pi-KVM + div(class="buttons buttons-row") + button(disabled data-force-hide-menu id="hid-recorder-record" class="row25") • Rec + button(disabled id="hid-recorder-stop" class="row25") Stop + button(disabled id="hid-recorder-play" class="row25") Play + button(disabled id="hid-recorder-clear" class="row25") Clear + hr + table(class="kv") + tr + td Script time: + td(colspan="2" id="hid-recorder-time" class="value") 00:00:00.0 + tr + td Scripted events: + td(id="hid-recorder-events-count" class="value") 0 + td #[sup #[i include delays]] + hr + input(type="file" id="hid-recorder-new-script-file") + div(class="buttons buttons-row") + button(disabled id="hid-recorder-upload" class="row50") Upload script + button(disabled id="hid-recorder-download" class="row50") Download script + + li(class="right") + a(class="menu-button" href="#") Shortcuts ↴ + div(data-dont-hide-menu class="menu") + div(class="buttons") + textarea(id="hid-pak-text" placeholder="Paste your text here") + hr + button(disabled data-force-hide-menu id="hid-pak-button") • ↳ Paste-as-Keys #[sup #[i ascii-only]] + hr + div(class="buttons-row") + button(data-force-hide-menu data-shortcut="CapsLock" class="row50") + | • Caps Lock   + img(class="inline-lamp hid-keyboard-caps-led led-gray" src=`${svg_dir}/led-square.svg`) + button(data-force-hide-menu data-shortcut="MetaLeft" class="row50") • Left Win + hr + button(data-force-hide-menu data-shortcut="AltLeft ShiftLeft") • Alt+Shift + button(data-force-hide-menu data-shortcut="ControlLeft ShiftLeft") • Ctrl+Shift + button(data-force-hide-menu data-shortcut="ShiftLeft ShiftRight") • Shift+Shift + button(data-force-hide-menu data-shortcut="MetaLeft Space") • Win+Space + hr + button(data-force-hide-menu data-shortcut="ControlLeft KeyW") • Ctrl+W + button(data-force-hide-menu data-shortcut="AltLeft Tab") • Alt+Tab + button(data-force-hide-menu data-shortcut="AltLeft Enter") • Alt+Enter + button(data-force-hide-menu data-shortcut="AltLeft F4") • Alt+F4 + hr + button(data-force-hide-menu data-shortcut="ControlLeft AltLeft Delete") • Ctrl+Alt+Del + hr + div(class="text") + | ↓ Alt+SysRq+... linux magic + | #[a(target="_blank" href="https://www.kernel.org/doc/html/latest/admin-guide/sysrq.html") help] + hr + div(class="buttons") + div(class="buttons-row") + each key in ["R", "E", "I", "S", "U", "B"] + button(data-shortcut=`AltLeft PrintScreen Key${key}` class="row16") #{key} diff --git a/web/kvm/window-about.pug b/web/kvm/window-about.pug new file mode 100644 index 00000000..92873ede --- /dev/null +++ b/web/kvm/window-about.pug @@ -0,0 +1,58 @@ ++window("about-window", "About", true, false) + div(id="about") + table + tr + td(valign="top" class="logo") + img(class="svg-gray" src="../share/svg/logo.svg" alt="Open Source Hardware" height="40") + td(valign="top") + table + tr #[td(colspan="2" class="title") Open Source & Open Hardware IP-KVM] + tr #[td(colspan="2" class="copyright") Copyright © 2018 Pi-KVM Developers Team] + br + div(class="tabs") + each tab, index in ["Meta", "Hardware", "Version", "Thanks"] + - tab_id = `about-tab-${tab != "Hardware" ? tab.toLowerCase() : "hw"}-button` + if index == 0 + input(checked type="radio" name="about-tab-button" id=tab_id) + else + input(type="radio" name="about-tab-button" id=tab_id) + label(for=tab_id) #{tab} + each tab in ["meta", "hw", "version"] + div(id=`about-tab-${tab}-content`) + div(id=`about-${tab}` class="code") #[span(class="code-comment") No data] + div(id="about-tab-thanks-content") + div(id="about-thanks" class="code") + span(class="code-comment") + | // These kind people donated money to the Pi-KVM project#[br] + | // and supported the work on it. We are very grateful#[br] + | // for their help, and memorializing their names#[br] + | // is the least we can do in gratitude.#[br] + | // If you also want to support this project,#[br] + | // you can use one of these services: + | #[a(target="_blank" href="https://www.patreon.com/pikvm") Patreon] + | or #[a(target="_blank" href="https://www.paypal.me/mdevaev") PayPal]. + ul + li Aleksei Brusianskii + li Arthur Woimbée + li Ben Gordon + li Branden Shaulis + li Christof Maluck + li Corey Lista + li David Howell + li Denis Yatsenko + li Ge Men + li Grey Cynic + li Jason Toland + li Jeff Bowman + li John McGovern + li Mark Gilbert + li Mark Robinson + li Mauricio Allende + li Michael Lynch + li Samed Ozoglu + li Truman Kilen + li Walter_Ego + br + p(class="text") + | Full documentation, source code, hardware schematics and legal information + | can be found in our #[a(target="_blank" href="https://pikvm.org") official website]. diff --git a/web/kvm/window-keyboard.pug b/web/kvm/window-keyboard.pug new file mode 100644 index 00000000..c2acb43f --- /dev/null +++ b/web/kvm/window-keyboard.pug @@ -0,0 +1,157 @@ +mixin key(code, classes="", width=0) + div(data-code=code, class=`key ${classes}`, style=(width ? `width:${width}px` : "")) + div(class="label") + block + +mixin modifier(code, classes="", width=0) + div(data-code=code class=`modifier ${classes}` style=(width ? `width:${width}px` : "")) + div(class="label") + | #[b •]#[br] + block + +mixin empty_key(width=0) + div(class="empty-key" style=(width ? `width:${width}px` : "")) + +mixin lamp(cls) + img(class=`inline-lamp ${cls} led-gray` src=`${svg_dir}/led-square.svg`) + ++window("keyboard-window", "Virtual Keyboard", true, true) + div(id="keyboard-desktop" class="keypad" align="center") + div(class="keypad-block") + div(class="keypad-row") + +key("Escape", "small") Esc + +empty_key(24) + each key in ["F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12"] + +key(key, "small") #{key} + if key == "F4" || key == "F8" + +empty_key(10) + hr + div(class="keypad-row") + +key("Backquote") ~#[br]` + each key, index in ["!", "@", "#", "$", "%", "^", "&", "*", "("] + +key(`Digit${index + 1}`) #{key}#[br]#{index + 1} + +key("Digit0") )#[br]0 + +key("Minus") _#[br]- + +key("Equal") +#[br]= + +key("Backspace", "wide-2 right") ↤ + div(class="keypad-row") + +key("Tab", "wide-2 left") ⇤#[br]⇥ + each key in ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"] + +key(`Key${key}`, "single") #{key} + +key("BracketLeft") {#[br][ + +key("BracketRight") }#[br]] + +key("Backslash") |#[br]\ + div(class="keypad-row") + +key("CapsLock", "wide-3 left small") + +lamp("hid-keyboard-caps-led") + | #[br] Caps Lock + each key in ["A", "S", "D", "F", "G", "H", "J", "K", "L"] + +key(`Key${key}`, "single") #{key} + +key("Semicolon") :#[br]; + +key("Quote") "#[br]' + +key("Enter", "wide-3 right small") Enter#[br]↵ + div(class="keypad-row") + +modifier("ShiftLeft", "wide-4 left small") Shift + each key in ["Z", "X", "C", "V", "B", "N", "M"] + +key(`Key${key}`, "single") #{key} + +key("Comma") <#[br], + +key("Period") >#[br]. + +key("Slash") ?#[br]/ + +modifier("ShiftRight", "wide-4 right small") Shift + div(class="keypad-row") + +modifier("ControlLeft", "wide-1 left small") Ctrl + +modifier("MetaLeft", "wide-1 left small") Win + +modifier("AltLeft", "wide-1 left small") Alt + +key("Space", "wide-5") + +modifier("AltRight", "wide-1 right small") Alt + +modifier("MetaRight", "wide-1 right small") Win + +modifier("ControlRight", "wide-1 right small") Ctrl + div(class="keypad-block") + div(class="keypad-row") + +modifier("PrintScreen", "small") Pt/Sq + +key("ScrollLock", "small") + +lamp("hid-keyboard-scroll-led") + | #[br] ScrLk + +key("Pause", "small") P/Brk + hr + div(class="keypad-row") + +key("Insert", "small") Ins + +key("Home", "small") Home + +key("PageUp", "small") PgUp + div(class="keypad-row") + +key("Delete", "small") Del + +key("End", "small") End + +key("PageDown", "small") PgDn + div(class="keypad-row") + div(class="keypad-row") + +empty_key() + +key("ArrowUp") ↑ + +empty_key() + div(class="keypad-row") + +key("ArrowLeft") ← + +key("ArrowDown") ↓ + +key("ArrowRight") → + + div(id="keyboard-mobile" class="keypad" align="center") + div(class="keypad-block") + div(class="keypad-row") + +key("Escape", "margin-0 small") Esc + +empty_key(1) + each key in ["F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12"] + +key(key, "wide-0 margin-0 small") #{key} + +empty_key(2) + +modifier("PrintScreen", "margin-0 small") Pt/Sq + +key("ScrollLock", "margin-0 small") + +lamp("hid-keyboard-scroll-led") + | #[br] ScrLk + +key("Pause", "margin-0 small") P/Brk + +key("Insert", "margin-0 small") Ins + +key("Home", "margin-0 small") Home + +key("End", "margin-0 small") End + +key("Delete", "margin-0 small") Del + div(class="keypad-row") + +key("Backquote") ~#[br]` + each key, index in ["!", "@", "#", "$", "%", "^", "&", "*", "("] + +key(`Digit${index + 1}`) #{key}#[br]#{index + 1} + +key("Digit0") )#[br]0 + +key("Minus") _#[br]- + +key("Equal") +#[br]= + +key("Backspace", "wide-3 right", 101) ↤ + div(class="keypad-row") + +key("Tab", "wide-2 left") ⇤
⇥ + each key in ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"] + +key(`Key${key}`, "single") #{key} + +key("BracketLeft") {#[br][ + +key("BracketRight") }#[br]] + +key("Backslash", "wide-2 left", 78) |#[br]\ + div(class="keypad-row") + +key("CapsLock", "wide-3 left small") + +lamp("hid-keyboard-caps-led") + | #[br] Caps Lock + each key in ["A", "S", "D", "F", "G", "H", "J", "K", "L"] + +key(`Key${key}`, "single") #{key} + +key("Semicolon") :#[br]; + +key("Quote") `#[br]' + +key("Enter", "wide-4 right small", 116) Enter#[br]↵ + div(class="keypad-row") + +modifier("ShiftLeft", "wide-4 left small") Shift + each key in ["Z", "X", "C", "V", "B", "N", "M"] + +key(`Key${key}`, "single") #{key} + +key("Comma") lt;#[br], + +key("Period") >#[br]. + +key("Slash") ?#[br]/ + +key("PageUp", "small") PgUp + +key("ArrowUp") ↑ + +key("PageDown", "small") PgDn + div(class="keypad-row") + +modifier("ControlLeft", "wide-1 left small") Ctrl + +modifier("MetaLeft", "wide-1 left small") Win + +modifier("AltLeft", "wide-1 left small") Alt + +key("Space", "", 190) + +modifier("AltRight", "right small") Alt + +modifier("MetaRight", "right small") Win + +modifier("ShiftRight", "right small") Shift + +modifier("ControlRight", "right small") Ctrl + +key("ArrowLeft") ← + +key("ArrowDown") ↓ + +key("ArrowRight") → diff --git a/web/kvm/window-stream.pug b/web/kvm/window-stream.pug new file mode 100644 index 00000000..a86c1812 --- /dev/null +++ b/web/kvm/window-stream.pug @@ -0,0 +1,15 @@ ++window("stream-window", "Stream", false, true) + div(id="stream-info") + div(id="stream-box" class="stream-box-inactive") + img(id="stream-image" class="stream-image-inactive" src=`${png_dir}/blank-stream.png`) + div(id="stream-mouse-buttons" class="keypad" align="center") + div(class="keypad-block") + div(class="keypad-row") + div(data-code="left" class="key wide-4 left small") #[span Mouse#[br]Left] + div(data-code="left" class="modifier wide-2 left small") #[span #[b •]#[br]← Hold] + div(class="empty-key" style="width:10px") + div(data-code="middle" class="key wide-2 left small") #[span Mouse#[br]Middle] + div(data-code="middle" class="modifier wide-2 left small") #[span #[b •]#[br]← Hold] + div(class="empty-key" style="width:10px") + div(data-code="right" class="modifier wide-2 right small") #[span #[b •]#[br]Hold →] + div(data-code="right" class="key wide-4 right small") #[span Mouse#[br]Right] diff --git a/web/kvm/windows.pug b/web/kvm/windows.pug new file mode 100644 index 00000000..21e17e0a --- /dev/null +++ b/web/kvm/windows.pug @@ -0,0 +1,11 @@ +mixin window(id, title, closeable=true, with_header_id=false) + div(id=id class="window") + div(id=(with_header_id ? `${id}-header` : "") class="window-header" style=(closeable ? "" : "z-index:1")) + div(class="window-grab") #{title} + if closeable + button(class="window-button-close") × + block + +include window-stream.pug +include window-keyboard.pug +include window-about.pug diff --git a/web/share/css/menu.css b/web/share/css/navbar.css similarity index 66% rename from web/share/css/menu.css rename to web/share/css/navbar.css index 0a84b303..c52a6050 100644 --- a/web/share/css/menu.css +++ b/web/share/css/navbar.css @@ -20,12 +20,12 @@ *****************************************************************************/ -ul#menu { +ul#navbar { box-shadow: var(--shadow-small); list-style-type: none; margin: 0; padding: 0; - background-color: var(--cs-menu-default-bg); + background-color: var(--cs-navbar-default-bg); position: fixed; top: 0; width: 100%; @@ -33,107 +33,107 @@ ul#menu { z-index: 2147483646; } -ul#menu li.menu-right-items { - border-left: var(--border-menu-thin); +ul#navbar li.right { + border-left: var(--border-navbar-item-thin); float: right; } -ul#menu li.menu-left-items { - border-right: var(--border-menu-thin); +ul#navbar li.left { + border-right: var(--border-navbar-item-thin); float: left; } -ul#menu li a#menu-logo { +ul#navbar li a#logo { line-height: 50px; outline: none; cursor: pointer; display: inline-block; - color: var(--cs-menu-default-fg); + color: var(--cs-navbar-default-fg); padding-left: 16px; padding-right: 16px; text-decoration: none; } -ul#menu li a.menu-item img { +ul#navbar li a.menu-button { + line-height: 50px; + outline: none; + cursor: pointer; + display: inline-block; + color: var(--cs-navbar-default-fg); + padding-left: 16px; + padding-right: 16px; + text-decoration: none; +} + +ul#navbar li a#logo:hover:not(.active), +ul#navbar li a.menu-button:hover:not(.active) { + background-color: var(--cs-navbar-item-hovered-bg); +} +@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) { + /* iPad 8 */ + ul#navbar li a#menu-button:hover:not(.active), + ul#navbar li a.menu-button:hover:not(.active) { + background-color: var(--cs-navbar-default-bg) !important; + } +} + +ul#navbar li a#logo img { + margin-top: -2px; + height: 24px; +} + +ul#navbar li a.menu-button img { vertical-align: middle; margin-right: 10px; height: 20px; } -ul#menu li a#menu-logo img { - margin-top: -2px; - height: 24px; +ul#navbar li a.menu-button-pressed { + box-shadow: var(--shadow-navbar-item-pressed); + background-color: var(--cs-navbar-item-pressed-bg) !important; } -ul#menu li a.menu-item { - line-height: 50px; - outline: none; - cursor: pointer; - display: inline-block; - color: var(--cs-menu-default-fg); - padding-left: 16px; - padding-right: 16px; - text-decoration: none; -} - -ul#menu li a#menu-logo:hover:not(.active), -ul#menu li a.menu-item:hover:not(.active) { - background-color: var(--cs-menu-hovered-bg); -} -@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) { - /* iPad 8 */ - ul#menu li a#menu-item:hover:not(.active), - ul#menu li a.menu-item:hover:not(.active) { - background-color: var(--cs-menu-default-bg) !important; - } -} - -ul#menu li a.menu-item-selected { - box-shadow: var(--shadow-menu-pressed); - background-color: var(--cs-menu-pressed-bg) !important; -} - -ul#menu li div.menu-item-content { +ul#navbar li div.menu { visibility: hidden; outline: none; overflow: hidden; white-space: nowrap; - border: var(--border-menu-item-content-default-2px); - border-top: var(--border-menu-item-content-top-thin); + border: var(--border-navbar-menu-default-2px); + border-top: var(--border-navbar-menu-top-thin); border-radius: 0 0 8px 8px; position: absolute; - background-color: var(--cs-menu-default-bg); + background-color: var(--cs-navbar-default-bg); min-width: 180px; box-shadow: var(--shadow-big); z-index: 2147483645; } -ul#menu li div.menu-item-content-active { - border: var(--border-menu-item-content-active-2px) !important; - border-top: var(--border-menu-item-content-top-thin) !important; +ul#navbar li div.menu-active { + border: var(--border-navbar-menu-active-2px) !important; + border-top: var(--border-navbar-menu-top-thin) !important; } -ul#menu li div.menu-item-content-buttons { +ul#navbar li div.menu div.buttons { background-color: var(--cs-control-default-bg); } -ul#menu li div.menu-item-content-text { +ul#navbar li div.menu div.text { margin: 10px 15px 10px 15px; font-size: 14px; } -ul#menu li div.menu-item-content-text table.one-line-switch { +ul#navbar li div.menu div.text table.one-line-switch { width: 100%; border-collapse: collapse; } @media only screen and (min-width: 768px) and (max-width: 1024px) and (orientation: portrait) { @supports (-webkit-appearance: none) { - ul#menu li div.menu-item-content-text table.one-line-switch { + ul#navbar li div.menu div.text table.one-line-switch { margin: 20px 0 20px 0 !important; } } } -ul#menu li div.menu-item-content table.menu-item-content-kv { +ul#navbar li div.menu table.kv { -webkit-user-select: text; -moz-user-select: text; user-select: text; @@ -141,14 +141,14 @@ ul#menu li div.menu-item-content table.menu-item-content-kv { margin: 0 10px 0 10px; font-size: 12px; } -ul#menu li div.menu-item-content table.menu-item-content-kv td.value { +ul#navbar li div.menu table.kv td.value { font-weight: bold; max-width: 310px; overflow: hidden; } -ul#menu li div.menu-item-content-buttons button, -ul#menu li div.menu-item-content-buttons select { +ul#navbar li div.menu div.buttons button, +ul#navbar li div.menu div.buttons select { box-shadow: none; border: none; border-radius: 0; @@ -156,7 +156,7 @@ ul#menu li div.menu-item-content-buttons select { padding: 0 16px; } -ul#menu li div.menu-item-content hr { +ul#navbar li div.menu hr { margin: 0; display: block; height: 0px; @@ -165,7 +165,7 @@ ul#menu li div.menu-item-content hr { border-top: var(--border-control-thin); } -ul#menu li div.menu-item-content img.sign { +ul#navbar li div.menu img.sign { vertical-align: middle; margin-right: 10px; height: 20px; diff --git a/web/share/css/vars.css b/web/share/css/vars.css index d4e5f49b..46ccf0a7 100644 --- a/web/share/css/vars.css +++ b/web/share/css/vars.css @@ -34,10 +34,10 @@ --cs-control-pressed-fg: #6c7481; --cs-control-disabled-fg: #6c7481; - --cs-menu-default-bg: #202225; - --cs-menu-default-fg: #c3c3c3; - --cs-menu-hovered-bg: #1a1c1f; - --cs-menu-pressed-bg: #171717; + --cs-navbar-default-bg: #202225; + --cs-navbar-default-fg: #c3c3c3; + --cs-navbar-item-hovered-bg: #1a1c1f; + --cs-navbar-item-pressed-bg: #171717; --cs-window-default-bg: #484b51; --cs-window-default-fg: #c3c3c3; @@ -69,18 +69,18 @@ --shadow-micro: 1px 2px 4px 0 rgba(0, 0, 0, 0.4); --shadow-small: 0 2px 4px 0 rgba(0, 0, 0, 0.2); --shadow-big: 0 8px 16px 0 rgba(0, 0, 0, 0.4); - --shadow-menu-pressed: 0 5px 0 #5b90bb inset; + --shadow-navbar-item-pressed: 0 5px 0 #5b90bb inset; --border-default-thin: thin solid #36393f; --border-default-2px: 2px solid #36393f; - --border-menu-thin: thin solid black; + --border-navbar-item-thin: thin solid black; --border-control-thin: thin solid #17191d; --border-window-thin: thin solid #17191d; --border-key-thin: thin solid #202225; --border-intensive-2px: 2px solid #5b90bb; --border-intensive-thin: thin solid #5b90bb; - --border-menu-item-content-default-2px: 2px solid black; - --border-menu-item-content-active-2px: 2px solid #5b90bb; + --border-navbar-menu-default-2px: 2px solid black; + --border-navbar-menu-active-2px: 2px solid #5b90bb; --border-menu-item-content-top-thin: thin solid #17191d; } diff --git a/web/share/js/wm.js b/web/share/js/wm.js index 9369d0c0..e53b8520 100644 --- a/web/share/js/wm.js +++ b/web/share/js/wm.js @@ -39,7 +39,7 @@ function __WindowManager() { var __top_z_index = 0; var __windows = []; - var __menu_items = []; + var __menu_buttons = []; var __init__ = function() { for (let el_button of $$$("button")) { @@ -48,10 +48,10 @@ function __WindowManager() { el_button.ontouchstart = function() {}; } - for (let el_item of $$("menu-item")) { - el_item.parentElement.querySelector(".menu-item-content").setAttribute("tabindex", "-1"); - tools.setOnDown(el_item, () => __toggleMenu(el_item)); - __menu_items.push(el_item); + for (let el_button of $$("menu-button")) { + el_button.parentElement.querySelector(".menu").setAttribute("tabindex", "-1"); + tools.setOnDown(el_button, () => __toggleMenu(el_button)); + __menu_buttons.push(el_button); } for (let el_window of $$("window")) { @@ -161,7 +161,7 @@ function __WindowManager() { let el_to_focus = ( el.closest(".modal-window") || el.closest(".window") - || el.closest(".menu-item-content") + || el.closest(".menu") ); if (el_to_focus) { el_to_focus.focus(); @@ -182,9 +182,9 @@ function __WindowManager() { }; self.getViewGeometry = function() { - let el_menu = $("menu"); + let el_navbar = $("navbar"); return { - top: (el_menu ? el_menu.clientHeight : 0), // Menu height + top: (el_navbar ? el_navbar.clientHeight : 0), // Navbar height bottom: Math.max(document.documentElement.clientHeight, window.innerHeight || 0), left: 0, right: Math.max(document.documentElement.clientWidth, window.innerWidth || 0), @@ -194,15 +194,15 @@ function __WindowManager() { var __toggleMenu = function(el_a) { let all_hidden = true; - for (let el_item of __menu_items) { - let el_menu = el_item.parentElement.querySelector(".menu-item-content"); - if (el_item === el_a && window.getComputedStyle(el_menu, null).visibility === "hidden") { - el_item.classList.add("menu-item-selected"); + for (let el_button of __menu_buttons) { + let el_menu = el_button.parentElement.querySelector(".menu"); + if (el_button === el_a && window.getComputedStyle(el_menu, null).visibility === "hidden") { + el_button.classList.add("menu-button-pressed"); el_menu.style.visibility = "visible"; el_menu.focus(); all_hidden &= false; } else { - el_item.classList.remove("menu-item-selected"); + el_button.classList.remove("menu-button-pressed"); el_menu.style.visibility = "hidden"; } } @@ -223,9 +223,9 @@ function __WindowManager() { var __closeAllMenues = function() { document.onkeyup = null; - for (let el_item of __menu_items) { - let el_menu = el_item.parentElement.querySelector(".menu-item-content"); - el_item.classList.remove("menu-item-selected"); + for (let el_button of __menu_buttons) { + let el_menu = el_button.parentElement.querySelector(".menu"); + el_button.classList.remove("menu-button-pressed"); el_menu.style.visibility = "hidden"; } }; @@ -236,8 +236,8 @@ function __WindowManager() { el_parent.classList.add("window-active"); } else if ((el_parent = event.target.closest(".window")) !== null) { el_parent.classList.add("window-active"); - } else if ((el_parent = event.target.closest(".menu-item-content")) !== null) { - el_parent.classList.add("menu-item-content-active"); + } else if ((el_parent = event.target.closest(".menu")) !== null) { + el_parent.classList.add("menu-active"); } tools.debug("Focus in:", el_parent); }; @@ -248,14 +248,14 @@ function __WindowManager() { el_parent.classList.remove("window-active"); } else if ((el_parent = event.target.closest(".window")) !== null) { el_parent.classList.remove("window-active"); - } else if ((el_parent = event.target.closest(".menu-item-content")) !== null) { - el_parent.classList.remove("menu-item-content-active"); + } else if ((el_parent = event.target.closest(".menu")) !== null) { + el_parent.classList.remove("menu-active"); } tools.debug("Focus out:", el_parent); }; var __globalMouseButtonHandler = function(event) { - if (!event.target.matches(".menu-item")) { + if (!event.target.matches(".menu-button")) { for (let el_item = event.target; el_item && el_item !== document; el_item = el_item.parentNode) { if (el_item.hasAttribute("data-force-hide-menu")) { break;