├── .gitignore ├── LICENSE ├── README.md ├── meson.build └── qt-video-wlr-bg.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | core 3 | *-protocol.h 4 | *-protocol.c 5 | *.o 6 | build 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 xdavidwu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qt-video-wlr 2 | 3 | Qt pip-mode-like video player for wlroots based wayland compositors. 4 | 5 | To install: 6 | 7 | ```sh 8 | meson build 9 | cd build 10 | ninja 11 | sudo ninja install 12 | ``` 13 | 14 | To run: 15 | 16 | ``` 17 | Usage: qt-video-wlr [options] FILE... 18 | Qt pip-mode-like video player for wlroots based wayland compositor 19 | 20 | Options: 21 | -h, --help Displays this help. 22 | -v, --version Displays version information. 23 | -l, --layer Layer to render on, background, bottom, top or 24 | overlay. Defaults to top. 25 | -w, --width Widget width. 0 to use max width. Defaults to 320. 26 | -e, --height Widget height. 0 to use max height. Defaults to 27 | 240. 28 | -c, --color Background color, QColor::setNamedColor() format. 29 | -s, --volume Linear sound volume, [0,100]. Defaults to 100. 30 | -p, --position Widget position, center, top, top-left, top-right, 31 | bottom, bottom-left, bottom-right, left ro right. 32 | Defaults to bottom-right. 33 | -n, --no-loop Do not loop. Defaults to loop. 34 | -m, --margin Widget margin. Edge-specific options take 35 | precedence if specified. No effect when not 36 | sticking to the edge. Defaults to 0. 37 | --top-margin Widget top margin. Defaults to 0. 38 | --right-margin Widget right margin. Defaults to 0. 39 | --bottom-margin Widget bottom margin. Defaults to 0. 40 | --left-margin Widget left margin. Defaults to 0. 41 | 42 | Arguments: 43 | FILE Files to play. 44 | ``` 45 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('qt-video-wlr', ['cpp']) 2 | 3 | qt5_dep = dependency('qt5', modules: ['Widgets', 'Core', 'Multimedia', 'MultimediaWidgets', 'Gui']) 4 | layer_shell_qt_dep = dependency('LayerShellQt', modules: ['LayerShellQt::Interface']) 5 | executable('qt-video-wlr', 6 | ['qt-video-wlr-bg.cpp'], 7 | dependencies: [qt5_dep, layer_shell_qt_dep], 8 | install: true) 9 | -------------------------------------------------------------------------------- /qt-video-wlr-bg.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | int main(int argc,char *argv[]){ 16 | LayerShellQt::Shell::useLayerShell(); 17 | QApplication::setApplicationName("qt-video-wlr"); 18 | QApplication::setApplicationVersion("dev"); 19 | // XXX: hack to make QWidget::windowHandle available before show 20 | // removed @ Qt6 21 | QApplication::setAttribute(Qt::AA_ImmediateWidgetCreation); 22 | 23 | QApplication app(argc, argv); 24 | QWidget root; 25 | 26 | QCommandLineParser parser; 27 | parser.setApplicationDescription("Qt pip-mode-like video player " 28 | "for wlroots based wayland compositor"); 29 | parser.addHelpOption(); 30 | parser.addVersionOption(); 31 | parser.addPositionalArgument("FILE", "Files to play.", "FILE..."); 32 | QCommandLineOption layerOption({"l", "layer"}, 33 | "Layer to render on, background, bottom, top or overlay." 34 | " Defaults to top.", "layer", "top"); 35 | QCommandLineOption widthOption({"w", "width"}, 36 | "Widget width. 0 for max width. Defaults to 320.", "width", "320"); 37 | QCommandLineOption heightOption({"e", "height"}, 38 | "Widget height. 0 for max height. Defaults to 240.", "height", "240"); 39 | QCommandLineOption colorOption({"c", "color"}, 40 | "Background color, QColor::setNamedColor() format.", "color"); 41 | QCommandLineOption volumeOption({"s", "volume"}, 42 | "Linear sound volume, [0,100]. Defaults to 100.", "volume"); 43 | QCommandLineOption positionOption({"p", "position"}, 44 | "Widget position: center, top, top-left, top-right, " 45 | "bottom, bottom-left, bottom-right, left or right. " 46 | "Defaults to bottom-right.", "position", "bottom-right"); 47 | QCommandLineOption noLoopOption({"n", "no-loop"}, 48 | "Do not loop. Defaults to loop."); 49 | QCommandLineOption marginOption({"m", "margin"}, 50 | "Widget margin. Edge-specific options take precedence if specified. " 51 | "No effect when not sticking to the edge. Defaults to 0.", "margin", "0"); 52 | QCommandLineOption topMarginOption("top-margin", 53 | "Widget top margin. Defaults to 0.", "margin", "0"); 54 | QCommandLineOption rightMarginOption("right-margin", 55 | "Widget right margin. Defaults to 0.", "margin", "0"); 56 | QCommandLineOption bottomMarginOption("bottom-margin", 57 | "Widget bottom margin. Defaults to 0.", "margin", "0"); 58 | QCommandLineOption leftMarginOption("left-margin", 59 | "Widget left margin. Defaults to 0.", "margin", "0"); 60 | QCommandLineOption exclusiveZoneOption({"x", "exclusive-zone"}, 61 | "Exclusive zone distance, -1 to draw over panels. Defaults to 0.", "distance", "0"); 62 | parser.addOptions({ layerOption, widthOption, heightOption, colorOption, 63 | volumeOption, positionOption, noLoopOption, marginOption, 64 | topMarginOption, rightMarginOption, bottomMarginOption, 65 | leftMarginOption, exclusiveZoneOption }); 66 | parser.process(app); 67 | 68 | bool ok; 69 | 70 | uint32_t width, height; 71 | width = parser.value(widthOption).toInt(&ok); 72 | if (!ok) parser.showHelp(1); 73 | height = parser.value(heightOption).toInt(&ok); 74 | if (!ok) parser.showHelp(1); 75 | 76 | auto layerShell = LayerShellQt::Window::get(root.windowHandle()); 77 | LayerShellQt::Window::Layer layer; 78 | QString layerStr = parser.value(layerOption); 79 | if (layerStr == "background") 80 | layer = LayerShellQt::Window::LayerBackground; 81 | else if (layerStr == "bottom") 82 | layer = LayerShellQt::Window::LayerBottom; 83 | else if (layerStr == "top") 84 | layer = LayerShellQt::Window::LayerTop; 85 | else if (layerStr == "overlay") 86 | layer = LayerShellQt::Window::LayerOverlay; 87 | else 88 | parser.showHelp(1); 89 | layerShell->setLayer(layer); 90 | 91 | LayerShellQt::Window::Anchors anchor; 92 | QString positionStr = parser.value(positionOption); 93 | if (positionStr == "center") 94 | anchor = {}; 95 | else if (positionStr == "top") 96 | anchor = LayerShellQt::Window::Anchor::AnchorTop; 97 | else if (positionStr == "top-left") 98 | anchor = {LayerShellQt::Window::Anchor::AnchorTop, 99 | LayerShellQt::Window::Anchor::AnchorLeft}; 100 | else if (positionStr == "top-right") 101 | anchor = {LayerShellQt::Window::Anchor::AnchorTop, 102 | LayerShellQt::Window::Anchor::AnchorRight}; 103 | else if (positionStr == "bottom") 104 | anchor = LayerShellQt::Window::Anchor::AnchorBottom; 105 | else if (positionStr == "bottom-left") 106 | anchor = {LayerShellQt::Window::Anchor::AnchorBottom, 107 | LayerShellQt::Window::Anchor::AnchorLeft}; 108 | else if (positionStr == "bottom-right") 109 | anchor = {LayerShellQt::Window::Anchor::AnchorBottom, 110 | LayerShellQt::Window::Anchor::AnchorRight}; 111 | else if (positionStr == "left") 112 | anchor = LayerShellQt::Window::Anchor::AnchorLeft; 113 | else if (positionStr == "right") 114 | anchor = LayerShellQt::Window::Anchor::AnchorRight; 115 | else 116 | parser.showHelp(1); 117 | if (width == 0) 118 | anchor |= {LayerShellQt::Window::Anchor::AnchorLeft, 119 | LayerShellQt::Window::Anchor::AnchorRight}; 120 | if (height == 0) 121 | anchor |= {LayerShellQt::Window::Anchor::AnchorTop, 122 | LayerShellQt::Window::Anchor::AnchorBottom}; 123 | layerShell->setAnchors(anchor); 124 | 125 | int top_margin, right_margin, bottom_margin, left_margin; 126 | top_margin = right_margin = bottom_margin = left_margin = 127 | parser.value(marginOption).toInt(&ok); 128 | if (!ok) parser.showHelp(1); 129 | top_margin = parser.value(topMarginOption).toInt(&ok); 130 | if (!ok) parser.showHelp(1); 131 | right_margin = parser.value(rightMarginOption).toInt(&ok); 132 | if (!ok) parser.showHelp(1); 133 | bottom_margin = parser.value(bottomMarginOption).toInt(&ok); 134 | if (!ok) parser.showHelp(1); 135 | left_margin = parser.value(leftMarginOption).toInt(&ok); 136 | if (!ok) parser.showHelp(1); 137 | layerShell->setMargins({left_margin, top_margin, right_margin, bottom_margin}); 138 | 139 | int32_t exclusive_zone = parser.value(exclusiveZoneOption).toInt(&ok); 140 | if (!ok || exclusive_zone < -1) parser.showHelp(1); 141 | layerShell->setExclusiveZone(exclusive_zone); 142 | 143 | layerShell->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityNone); 144 | 145 | layerShell->setDesiredOutput(nullptr); 146 | 147 | QVBoxLayout layout; 148 | QVideoWidget videoWidget; 149 | layout.addWidget(&videoWidget); 150 | layout.setContentsMargins({}); 151 | root.setLayout(&layout); 152 | root.resize(width ? width : 1, height ? height : 1); 153 | root.show(); 154 | 155 | QMediaPlayer player; 156 | QMediaPlaylist playlist; 157 | QStringList pos_args = parser.positionalArguments(); 158 | if (pos_args.size() == 0) 159 | parser.showHelp(1); 160 | for (auto path: pos_args) 161 | playlist.addMedia( 162 | QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath())); 163 | if (!parser.isSet(noLoopOption)) 164 | playlist.setPlaybackMode(QMediaPlaylist::Loop); 165 | player.setPlaylist(&playlist); 166 | if (parser.isSet(volumeOption)) { 167 | player.setVolume(parser.value(volumeOption).toInt(&ok)); 168 | if (!ok) parser.showHelp(1); 169 | } 170 | 171 | player.setVideoOutput(&videoWidget); 172 | if (parser.isSet(colorOption)) { 173 | QColor qcolor(parser.value(colorOption)); 174 | if(!qcolor.isValid()) parser.showHelp(1); 175 | QPalette pal; 176 | pal.setColor(QPalette::Window, qcolor); 177 | videoWidget.setAutoFillBackground(true); 178 | videoWidget.setPalette(pal); 179 | QPalette pal2; 180 | pal2.setColor(QPalette::Window, "#00000000"); 181 | root.setAutoFillBackground(true); 182 | root.setPalette(pal2); 183 | } 184 | videoWidget.show(); 185 | player.play(); 186 | QObject::connect(&player, &QMediaPlayer::stateChanged, 187 | [&app](QMediaPlayer::State playerState) { 188 | if(playerState == QMediaPlayer::StoppedState){ 189 | app.quit(); 190 | } 191 | }); 192 | 193 | return app.exec(); 194 | } 195 | --------------------------------------------------------------------------------