oooo it workin
This commit is contained in:
commit
c16ed01fbe
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
.flatpak-builder/
|
||||
build/
|
6
build.sh
Executable file
6
build.sh
Executable file
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -xe
|
||||
|
||||
flatpak-builder build dog.asonix.git.asonix.owo.yml --user --install --force-clean
|
||||
flatpak run dog.asonix.git.asonix.owo
|
33
data/gschema.xml
Normal file
33
data/gschema.xml
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<schemalist>
|
||||
<schema path="/dog/asonix/git/asonix/owo/obs/" id="dog.asonix.git.asonix.owo.obs">
|
||||
<key name="host" type="s">
|
||||
<default>"localhost"</default>
|
||||
<summary>OBS Host</summary>
|
||||
<description>The hostname used to connect to OBS</description>
|
||||
</key>
|
||||
<key name="port" type="q">
|
||||
<default>4444</default>
|
||||
<summary>OBS Port</summary>
|
||||
<description>The port used to connect to OBS</description>
|
||||
</key>
|
||||
</schema>
|
||||
|
||||
<schema path="/dog/asonix/git/asonix/owo/saved-state/" id="dog.asonix.git.asonix.owo.saved-state">
|
||||
<key type="(ii)" name="window-position">
|
||||
<default>(-1, -1)</default>
|
||||
<summary>Window position</summary>
|
||||
<description>Most recent window position (x, y)</description>
|
||||
</key>
|
||||
<key type="(ii)" name="window-size">
|
||||
<default>(900, 600)</default>
|
||||
<summary>Most recent window size</summary>
|
||||
<description>Most recent window size (width, height)</description>
|
||||
</key>
|
||||
<key type="b" name="window-maximized">
|
||||
<default>false</default>
|
||||
<summary>Open window maximized.</summary>
|
||||
<description>Whether the main window of the application should open maximized or not.</description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
6
data/meson.build
Normal file
6
data/meson.build
Normal file
|
@ -0,0 +1,6 @@
|
|||
# Install our gschema.xml file so that we can write stateful information to GSettings
|
||||
install_data (
|
||||
'gschema.xml',
|
||||
install_dir: join_paths (get_option ('datadir'), 'glib-2.0', 'schemas'),
|
||||
rename: meson.project_name () + '.gschema.xml'
|
||||
)
|
15
data/owo.appdata.xml.in
Normal file
15
data/owo.appdata.xml.in
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop">
|
||||
<id>dog.asonix.git.asonix.owo</id>
|
||||
<metadata_license>AGPL3</metadata_license>
|
||||
<name>OwO</name>
|
||||
<summary>OwO: The Experience</summary>
|
||||
<description>
|
||||
<p>Oh this is html isn't it, isn't it, isn't it html</p>
|
||||
</description>
|
||||
<custom>
|
||||
<value key="x-appcenter-color-primary">#e0005c</value>
|
||||
<value key="x-appcenter-color-primary-text">rgb(255, 255, 255)</value>
|
||||
<value key="x-appcenter-suggested-price">0</value>
|
||||
</custom>
|
||||
</component>
|
9
data/owo.desktop.in
Normal file
9
data/owo.desktop.in
Normal file
|
@ -0,0 +1,9 @@
|
|||
[Desktop Entry]
|
||||
Name=OwO
|
||||
GenericName=OwO UwU
|
||||
Comment=Playing with GTK and elementary 6
|
||||
Categories=Utility;Education;
|
||||
Exec=dog.asonix.git.asonix.owo
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Keywords=Hello;World;Example;
|
20
dog.asonix.git.asonix.owo.yml
Normal file
20
dog.asonix.git.asonix.owo.yml
Normal file
|
@ -0,0 +1,20 @@
|
|||
app-id: dog.asonix.git.asonix.owo
|
||||
|
||||
runtime: io.elementary.Platform
|
||||
runtime-version: 'daily'
|
||||
sdk: io.elementary.Sdk
|
||||
|
||||
command: dog.asonix.git.asonix.owo
|
||||
|
||||
finish-args:
|
||||
- '--share=ipc'
|
||||
- '--socket=fallback-x11'
|
||||
- '--socket=wayland'
|
||||
- '--talk-name=dog.asonix.git.asonix.Streamdeck'
|
||||
|
||||
modules:
|
||||
- name: owo
|
||||
buildsystem: meson
|
||||
sources:
|
||||
- type: dir
|
||||
path: .
|
64
meson.build
Normal file
64
meson.build
Normal file
|
@ -0,0 +1,64 @@
|
|||
project('dog.asonix.git.asonix.owo', 'vala', 'c')
|
||||
|
||||
i18n = import('i18n')
|
||||
|
||||
add_global_arguments('-DGETTEXT_PACKAGE="@0@"'.format (meson.project_name()), language:'c')
|
||||
|
||||
app_files = files(
|
||||
'src/Application.vala',
|
||||
'src/Daemon.vala',
|
||||
'src/MainWindow.vala',
|
||||
'src/Data/Command.vala',
|
||||
'src/Data/SwitchScene.vala',
|
||||
'src/Dialogs/EditCommandDialog.vala',
|
||||
'src/Dialogs/NewCommandDialog.vala',
|
||||
'src/Views/ConfigCommand.vala',
|
||||
'src/Views/DeckStack.vala',
|
||||
'src/Views/DeckView.vala',
|
||||
'src/Views/ObsView.vala',
|
||||
'src/Widgets/CommandComboBox.vala',
|
||||
'src/Widgets/CommandList.vala',
|
||||
'src/Widgets/CommandRow.vala',
|
||||
'src/Widgets/DeckList.vala',
|
||||
'src/Widgets/DeckItem.vala',
|
||||
'src/Widgets/DisconnectedPage.vala',
|
||||
'src/Widgets/EmptyConfigPane.vala'
|
||||
)
|
||||
|
||||
executable(
|
||||
meson.project_name(),
|
||||
app_files,
|
||||
dependencies: [
|
||||
dependency('gdk-3.0'),
|
||||
dependency('gee-0.8'),
|
||||
dependency('gio-2.0'),
|
||||
dependency('granite'),
|
||||
dependency('gtk+-3.0'),
|
||||
dependency('json-glib-1.0'),
|
||||
dependency('libhandy-1'),
|
||||
dependency('pango')
|
||||
],
|
||||
install: true
|
||||
)
|
||||
|
||||
i18n.merge_file(
|
||||
input: join_paths('data', 'owo.desktop.in'),
|
||||
output: meson.project_name() + '.desktop',
|
||||
po_dir: join_paths(meson.source_root(), 'po'),
|
||||
type: 'desktop',
|
||||
install: true,
|
||||
install_dir: join_paths(get_option('datadir'), 'applications')
|
||||
)
|
||||
|
||||
i18n.merge_file(
|
||||
input: join_paths('data', 'owo.appdata.xml.in'),
|
||||
output: meson.project_name() + '.appdata.xml',
|
||||
po_dir: join_paths(meson.source_root(), 'po'),
|
||||
install: true,
|
||||
install_dir: join_paths(get_option('datadir'), 'metainfo')
|
||||
)
|
||||
|
||||
meson.add_install_script('meson/post_install.py')
|
||||
|
||||
subdir('po')
|
||||
subdir('data')
|
10
meson/post_install.py
Normal file
10
meson/post_install.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
schemadir = os.path.join(os.environ['MESON_INSTALL_PREFIX'], 'share', 'glib-2.0', 'schemas')
|
||||
|
||||
if not os.environ.get('DESTDIR'):
|
||||
print('Compiling gsettings schemas...')
|
||||
subprocess.call(['glib-compile-schemas', schemadir])
|
1
po/LINGUAS
Normal file
1
po/LINGUAS
Normal file
|
@ -0,0 +1 @@
|
|||
en
|
3
po/POTFILES
Normal file
3
po/POTFILES
Normal file
|
@ -0,0 +1,3 @@
|
|||
src/Application.vala
|
||||
data/owo.desktop.in
|
||||
data/owo.appdata.xml.in
|
58
po/dog.asonix.git.asonix.owo.pot
Normal file
58
po/dog.asonix.git.asonix.owo.pot
Normal file
|
@ -0,0 +1,58 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the dog.asonix.git.asonix.owo package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: dog.asonix.git.asonix.owo\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-04-30 21:33-0500\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: src/Application.vala:14
|
||||
msgid "Label 1"
|
||||
msgstr ""
|
||||
|
||||
#: src/Application.vala:15
|
||||
msgid "Label 2"
|
||||
msgstr ""
|
||||
|
||||
#: src/Application.vala:20
|
||||
msgid "Hewwo owo"
|
||||
msgstr ""
|
||||
|
||||
#: data/owo.desktop.in:3 data/owo.appdata.xml.in:5
|
||||
msgid "OwO"
|
||||
msgstr ""
|
||||
|
||||
#: data/owo.desktop.in:4
|
||||
msgid "OwO UwU"
|
||||
msgstr ""
|
||||
|
||||
#: data/owo.desktop.in:5
|
||||
msgid "Playing with GTK and elementary 6"
|
||||
msgstr ""
|
||||
|
||||
#: data/owo.desktop.in:8
|
||||
msgid "dog.asonix.git.asonix.owo"
|
||||
msgstr ""
|
||||
|
||||
#: data/owo.desktop.in:11
|
||||
msgid "Hello;World;Example;"
|
||||
msgstr ""
|
||||
|
||||
#: data/owo.appdata.xml.in:6
|
||||
msgid "OwO: The Experience"
|
||||
msgstr ""
|
||||
|
||||
#: data/owo.appdata.xml.in:8
|
||||
msgid "Oh this is html isn't it, isn't it, isn't it html"
|
||||
msgstr ""
|
58
po/en.po
Normal file
58
po/en.po
Normal file
|
@ -0,0 +1,58 @@
|
|||
# English translations for dog.asonix.git.asonix.owo package.
|
||||
# Copyright (C) 2021 THE dog.asonix.git.asonix.owo'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the dog.asonix.git.asonix.owo package.
|
||||
# Automatically generated, 2021.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: dog.asonix.git.asonix.owo\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-04-30 21:33-0500\n"
|
||||
"PO-Revision-Date: 2021-04-30 21:32-0500\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: en\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=ASCII\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/Application.vala:14
|
||||
msgid "Label 1"
|
||||
msgstr "Label 1"
|
||||
|
||||
#: src/Application.vala:15
|
||||
msgid "Label 2"
|
||||
msgstr "Label 2"
|
||||
|
||||
#: src/Application.vala:20
|
||||
msgid "Hewwo owo"
|
||||
msgstr ""
|
||||
|
||||
#: data/owo.desktop.in:3 data/owo.appdata.xml.in:5
|
||||
msgid "OwO"
|
||||
msgstr "OwO"
|
||||
|
||||
#: data/owo.desktop.in:4
|
||||
msgid "OwO UwU"
|
||||
msgstr "OwO UwU"
|
||||
|
||||
#: data/owo.desktop.in:5
|
||||
msgid "Playing with GTK and elementary 6"
|
||||
msgstr "Playing with GTK and elementary 6"
|
||||
|
||||
#: data/owo.desktop.in:8
|
||||
msgid "dog.asonix.git.asonix.owo"
|
||||
msgstr "dog.asonix.git.asonix.owo"
|
||||
|
||||
#: data/owo.desktop.in:11
|
||||
msgid "Hello;World;Example;"
|
||||
msgstr "Hello;World;Example;"
|
||||
|
||||
#: data/owo.appdata.xml.in:6
|
||||
msgid "OwO: The Experience"
|
||||
msgstr "OwO: The Experience"
|
||||
|
||||
#: data/owo.appdata.xml.in:8
|
||||
msgid "Oh this is html isn't it, isn't it, isn't it html"
|
||||
msgstr "Oh this is html isn't it, isn't it, isn't it html"
|
4
po/meson.build
Normal file
4
po/meson.build
Normal file
|
@ -0,0 +1,4 @@
|
|||
i18n.gettext(meson.project_name(),
|
||||
args: '--directory=' + meson.source_root(),
|
||||
preset: 'glib'
|
||||
)
|
38
src/Application.vala
Normal file
38
src/Application.vala
Normal file
|
@ -0,0 +1,38 @@
|
|||
public class Streamdeck.App : Gtk.Application {
|
||||
public static GLib.Settings obs_settings { get; private set; }
|
||||
public static GLib.Settings saved_state { get; private set; }
|
||||
public static MainWindow main_window { get; private set; }
|
||||
|
||||
static construct {
|
||||
obs_settings = new GLib.Settings ("dog.asonix.git.asonix.owo.obs");
|
||||
saved_state = new GLib.Settings ("dog.asonix.git.asonix.owo.saved-state");
|
||||
}
|
||||
|
||||
construct {
|
||||
flags = ApplicationFlags.FLAGS_NONE;
|
||||
application_id = "dog.asonix.git.asonix.owo";
|
||||
|
||||
var present_action = new SimpleAction ("app.present", null);
|
||||
present_action.activate.connect (() => {
|
||||
if (main_window != null) {
|
||||
main_window.present_with_time ((uint32) GLib.get_monotonic_time ());
|
||||
}
|
||||
});
|
||||
|
||||
add_action (present_action);
|
||||
}
|
||||
|
||||
protected override void activate () {
|
||||
if (main_window == null) {
|
||||
main_window = new MainWindow (this);
|
||||
main_window.build_ui ();
|
||||
add_window (main_window);
|
||||
}
|
||||
|
||||
main_window.present ();
|
||||
}
|
||||
|
||||
public static int main (string[] args) {
|
||||
return new Streamdeck.App ().run (args);
|
||||
}
|
||||
}
|
284
src/Daemon.vala
Normal file
284
src/Daemon.vala
Normal file
|
@ -0,0 +1,284 @@
|
|||
public struct Streamdeck.ReadInput {
|
||||
public uint8 key;
|
||||
public string serial_number;
|
||||
}
|
||||
|
||||
public struct Streamdeck.DeckInfo {
|
||||
public string serial_number;
|
||||
public string device_name;
|
||||
public string port_name;
|
||||
}
|
||||
|
||||
public struct Streamdeck.CommandInfo {
|
||||
public uint8 key;
|
||||
public string command;
|
||||
}
|
||||
|
||||
public class Streamdeck.Daemon : Object {
|
||||
[DBus (name = "dog.asonix.git.asonix.Streamdeck")]
|
||||
private interface StreamdeckBackend : Object {
|
||||
public async abstract string[] get_scenes () throws GLib.Error;
|
||||
public async abstract void enable_discovery () throws GLib.Error;
|
||||
public async abstract void disable_discovery () throws GLib.Error;
|
||||
public async abstract DeckInfo[] get_decks () throws GLib.Error;
|
||||
public async abstract string connect (string host, uint16 port) throws GLib.Error;
|
||||
public async abstract string disconnect () throws GLib.Error;
|
||||
public async abstract string get_state () throws GLib.Error;
|
||||
public async abstract string login (string password) throws GLib.Error;
|
||||
public async abstract CommandInfo[] get_commands (string serial_number) throws GLib.Error;
|
||||
public async abstract ReadInput[] read_input () throws GLib.Error;
|
||||
public async abstract void set_input (string serial_number, uint8 key, string command) throws GLib.Error;
|
||||
public async abstract void unset_input (string serial_number, uint8 key) throws GLib.Error;
|
||||
}
|
||||
|
||||
private static Daemon? _instance;
|
||||
|
||||
public static Daemon instance {
|
||||
get {
|
||||
if (_instance == null) {
|
||||
_instance = new Daemon ();
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private StreamdeckBackend? backend_object;
|
||||
private string? obs_state;
|
||||
private DeckInfo[] decks;
|
||||
private string[] scenes;
|
||||
private Gee.HashMap<string, Gee.ArrayList<CommandInfo?>> commands = new Gee.HashMap<string, Gee.ArrayList<CommandInfo?>> ();
|
||||
|
||||
public signal void dbus_connection_signal ();
|
||||
public signal void obs_state_signal (string state);
|
||||
public signal void decks_signal (DeckInfo[] decks);
|
||||
public signal void on_decks_added (DeckInfo[] decks);
|
||||
public signal void on_decks_removed (DeckInfo[] decks);
|
||||
public signal void scenes_signal (string[] scenes);
|
||||
public signal void commands_signal (string serial_number, CommandInfo[] commands);
|
||||
public signal void key_press_signal (ReadInput key_press);
|
||||
|
||||
construct {
|
||||
dbus_connection_signal.connect ((_obj) => {
|
||||
get_state.begin ((_obj, res) => {
|
||||
try {
|
||||
var state = get_state.end (res);
|
||||
obs_state_signal (state);
|
||||
} catch (Error e) {
|
||||
print ("Get state error: %s\n", e.message);
|
||||
}
|
||||
});
|
||||
|
||||
load_decks.begin ((_obj, res) => {
|
||||
try {
|
||||
var new_decks = load_decks.end (res);
|
||||
decks_signal (new_decks);
|
||||
} catch (Error e) {
|
||||
print ("Get decks error: %s\n", e.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
obs_state_signal.connect ((_obj, state) => {
|
||||
on_state_change.begin (state, (_obj, res) => {
|
||||
try {
|
||||
on_state_change.end (res);
|
||||
} catch (Error e) {
|
||||
print ("State handler error: %s\n", e.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
decks_signal.connect ((_obj, deck_list) => {
|
||||
var new_decks = diff_decks (decks, deck_list);
|
||||
var removed_decks = diff_decks (deck_list, decks);
|
||||
|
||||
decks = deck_list;
|
||||
|
||||
on_decks_added (new_decks);
|
||||
on_decks_removed (removed_decks);
|
||||
|
||||
foreach (DeckInfo deck_info in new_decks) {
|
||||
update_command_cache (deck_info.serial_number);
|
||||
}
|
||||
});
|
||||
|
||||
scenes_signal.connect ((_obj, new_scenes) => {
|
||||
scenes = new_scenes;
|
||||
});
|
||||
|
||||
commands_signal.connect ((_obj, serial_number, new_commands) => {
|
||||
var array_list = new Gee.ArrayList<CommandInfo?> ((a, b) => {
|
||||
return a.key == b.key && a.command == b.command;
|
||||
});
|
||||
|
||||
foreach (CommandInfo info in new_commands) {
|
||||
array_list.add (info);
|
||||
}
|
||||
|
||||
commands.set (serial_number, array_list);
|
||||
});
|
||||
|
||||
Bus.get_proxy.begin<StreamdeckBackend> (
|
||||
BusType.SESSION,
|
||||
"dog.asonix.git.asonix.Streamdeck",
|
||||
"/dog/asonix/git/asonix/Streamdeck",
|
||||
0,
|
||||
null,
|
||||
(_obj, res) => {
|
||||
try {
|
||||
backend_object = Bus.get_proxy.end (res);
|
||||
dbus_connection_signal ();
|
||||
} catch (Error e) {
|
||||
error ("Streamdeck error: %s", e.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public string get_obs_state () {
|
||||
if (obs_state == null) {
|
||||
return "Disconnected";
|
||||
} else {
|
||||
return obs_state;
|
||||
}
|
||||
}
|
||||
|
||||
public DeckInfo[] get_decks () {
|
||||
return decks;
|
||||
}
|
||||
|
||||
public string[] get_scenes () {
|
||||
return scenes;
|
||||
}
|
||||
|
||||
public Gee.ArrayList<CommandInfo?> get_commands (string serial_number) {
|
||||
return commands.get (serial_number);
|
||||
}
|
||||
|
||||
public async void add_command (string serial_number, uint8 key, string command) throws GLib.Error {
|
||||
disconnected_err ();
|
||||
|
||||
yield backend_object.set_input (serial_number, key, command);
|
||||
}
|
||||
|
||||
public async void remove_command (string serial_number, uint8 key) throws GLib.Error {
|
||||
disconnected_err ();
|
||||
|
||||
yield backend_object.unset_input (serial_number, key);
|
||||
}
|
||||
|
||||
public async void connect_obs () throws GLib.Error {
|
||||
disconnected_err ();
|
||||
|
||||
string host;
|
||||
uint16 port;
|
||||
App.obs_settings.get ("host", "s", out host);
|
||||
App.obs_settings.get ("port", "q", out port);
|
||||
|
||||
var state = yield backend_object.connect (host, port);
|
||||
obs_state_signal (state);
|
||||
}
|
||||
|
||||
public async void authenticate_obs () throws GLib.Error {
|
||||
disconnected_err ();
|
||||
|
||||
// TODO: secure passw0rt storage
|
||||
var passw0rt = "passw0rt";
|
||||
|
||||
var state = yield backend_object.login (passw0rt);
|
||||
obs_state_signal (state);
|
||||
}
|
||||
|
||||
public async void disconnect_obs () throws GLib.Error {
|
||||
disconnected_err ();
|
||||
|
||||
var state = yield backend_object.disconnect ();
|
||||
obs_state_signal (state);
|
||||
}
|
||||
|
||||
public async void key_press () throws GLib.Error {
|
||||
disconnected_err ();
|
||||
|
||||
var read_inputs = yield backend_object.read_input ();
|
||||
foreach (ReadInput input in read_inputs) {
|
||||
key_press_signal (input);
|
||||
}
|
||||
}
|
||||
|
||||
public void update_command_cache (string serial_number) {
|
||||
load_commands.begin (serial_number, (_obj, res) => {
|
||||
try {
|
||||
var new_commands = load_commands.end (res);
|
||||
commands_signal (serial_number, new_commands);
|
||||
} catch (Error e) {
|
||||
print ("Command fetch error: %s\n", e.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async string[] load_scenes () throws GLib.Error {
|
||||
disconnected_err ();
|
||||
|
||||
return yield backend_object.get_scenes ();
|
||||
}
|
||||
|
||||
private async DeckInfo[] load_decks () throws GLib.Error {
|
||||
disconnected_err ();
|
||||
|
||||
return yield backend_object.get_decks ();
|
||||
}
|
||||
|
||||
private async CommandInfo[] load_commands (string serial_number) throws GLib.Error {
|
||||
disconnected_err ();
|
||||
|
||||
return yield backend_object.get_commands (serial_number);
|
||||
}
|
||||
|
||||
private async void on_state_change (string state) throws GLib.Error {
|
||||
if (obs_state == state) {
|
||||
return;
|
||||
}
|
||||
|
||||
obs_state = state;
|
||||
|
||||
if (state == "Disconnected") {
|
||||
yield connect_obs ();
|
||||
} else if (state == "Unauthenticated") {
|
||||
yield authenticate_obs ();
|
||||
} else if (state == "Connected") {
|
||||
var new_scenes = yield load_scenes ();
|
||||
scenes_signal (new_scenes);
|
||||
}
|
||||
}
|
||||
|
||||
private async string get_state () throws GLib.Error {
|
||||
disconnected_err ();
|
||||
|
||||
return yield backend_object.get_state ();
|
||||
}
|
||||
|
||||
private void disconnected_err () throws GLib.Error {
|
||||
if (backend_object == null) {
|
||||
throw new GLib.IOError.FAILED ("Not connected to streamdeck daemon");
|
||||
}
|
||||
}
|
||||
|
||||
private DeckInfo[] diff_decks(DeckInfo[] lhs, DeckInfo[] rhs) {
|
||||
DeckInfo[] new_decks = new DeckInfo[0];
|
||||
|
||||
var exists = false;
|
||||
foreach (DeckInfo deck_info in rhs) {
|
||||
exists = false;
|
||||
foreach (DeckInfo existing_info in lhs) {
|
||||
exists = exists
|
||||
|| deck_info.serial_number == existing_info.serial_number;
|
||||
}
|
||||
if (!exists) {
|
||||
new_decks += deck_info;
|
||||
}
|
||||
}
|
||||
|
||||
return new_decks;
|
||||
}
|
||||
}
|
71
src/Data/Command.vala
Normal file
71
src/Data/Command.vala
Normal file
|
@ -0,0 +1,71 @@
|
|||
namespace Streamdeck.Data {
|
||||
public enum CommandType {
|
||||
SWITCH_SCENE,
|
||||
}
|
||||
|
||||
public struct CommandDescription {
|
||||
CommandType type;
|
||||
string name;
|
||||
string id;
|
||||
}
|
||||
|
||||
public abstract class Command : GLib.Object {
|
||||
private CommandType type;
|
||||
private uint8 key;
|
||||
|
||||
protected Command (uint8 key, CommandType type) {
|
||||
this.key = key;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public static CommandDescription[] available() {
|
||||
CommandDescription[] available = {
|
||||
CommandDescription() {
|
||||
type = CommandType.SWITCH_SCENE,
|
||||
name = _("Switch Scene"),
|
||||
id = "SwitchScene"
|
||||
}
|
||||
};
|
||||
|
||||
return available;
|
||||
}
|
||||
|
||||
public static CommandType? type_string (string? type) {
|
||||
switch (type) {
|
||||
case "SwitchScene":
|
||||
return CommandType.SWITCH_SCENE;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Command? parse (uint8 key, string command) {
|
||||
var parser = new Json.Parser ();
|
||||
|
||||
try {
|
||||
parser.load_from_data (command);
|
||||
var obj = parser.get_root ().get_object ();
|
||||
|
||||
switch (type_string(obj.get_string_member ("type"))) {
|
||||
case CommandType.SWITCH_SCENE:
|
||||
var scene_name = obj.get_string_member ("name");
|
||||
return new SwitchScene (key, scene_name);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract string to_json ();
|
||||
|
||||
public CommandType get_command_type () {
|
||||
return type;
|
||||
}
|
||||
|
||||
public uint8 get_command_key () {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
26
src/Data/SwitchScene.vala
Normal file
26
src/Data/SwitchScene.vala
Normal file
|
@ -0,0 +1,26 @@
|
|||
namespace Streamdeck.Data {
|
||||
public class SwitchScene : Command {
|
||||
public string scene_name;
|
||||
|
||||
public SwitchScene (uint8 key, string scene_name) {
|
||||
base (key, CommandType.SWITCH_SCENE);
|
||||
this.scene_name = scene_name;
|
||||
}
|
||||
|
||||
public override string to_json () {
|
||||
var gen = new Json.Generator ();
|
||||
var root = new Json.Node (Json.NodeType.OBJECT);
|
||||
var object = new Json.Object ();
|
||||
root.set_object (object);
|
||||
gen.set_root (root);
|
||||
|
||||
object.set_string_member ("type", "SwitchScene");
|
||||
object.set_string_member ("name", scene_name);
|
||||
|
||||
size_t length;
|
||||
string json = gen.to_data (out length);
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
||||
}
|
31
src/Dialogs/EditCommandDialog.vala
Normal file
31
src/Dialogs/EditCommandDialog.vala
Normal file
|
@ -0,0 +1,31 @@
|
|||
namespace Streamdeck.Dialogs {
|
||||
public class EditCommandDialog : Granite.Dialog {
|
||||
private Gtk.Stack stack;
|
||||
|
||||
public EditCommandDialog (string serial_number, Data.Command initial_command) {
|
||||
var disconnected_page = new Widgets.DisconnectedPage ();
|
||||
|
||||
var command_page = new Views.ConfigCommand.from_existing (serial_number, initial_command);
|
||||
|
||||
stack = new Gtk.Stack ();
|
||||
stack.add_named (disconnected_page, "disconnected");
|
||||
stack.add_named (command_page, "command");
|
||||
|
||||
get_content_area ().add (stack);
|
||||
|
||||
if (Daemon.instance.get_obs_state () != "Connected") {
|
||||
add_button ("Close", Gtk.ResponseType.REJECT);
|
||||
} else {
|
||||
add_button ("Close", Gtk.ResponseType.CLOSE);
|
||||
}
|
||||
|
||||
show_all ();
|
||||
|
||||
if (Daemon.instance.get_obs_state () != "Connected") {
|
||||
stack.set_visible_child_name ("disconnected");
|
||||
} else {
|
||||
stack.set_visible_child_name ("command");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
61
src/Dialogs/NewCommandDialog.vala
Normal file
61
src/Dialogs/NewCommandDialog.vala
Normal file
|
@ -0,0 +1,61 @@
|
|||
namespace Streamdeck.Dialogs {
|
||||
public class NewCommandDialog : Granite.Dialog {
|
||||
private string serial_number;
|
||||
private Gtk.Stack stack;
|
||||
private ReadInput key_info;
|
||||
|
||||
public NewCommandDialog (string serial_number) {
|
||||
this.serial_number = serial_number;
|
||||
|
||||
var disconnected_page = new Widgets.DisconnectedPage ();
|
||||
|
||||
var press_label = new Gtk.Label (
|
||||
_("Please press the button you wish to program on your stream deck")
|
||||
);
|
||||
press_label.valign = Gtk.Align.CENTER;
|
||||
press_label.get_style_context ().add_class (Granite.STYLE_CLASS_H3_LABEL);
|
||||
|
||||
var press_page = new Gtk.Grid ();
|
||||
press_page.column_spacing = 12;
|
||||
press_page.row_spacing = 12;
|
||||
press_page.halign = Gtk.Align.CENTER;
|
||||
press_page.margin = 24;
|
||||
|
||||
press_page.attach (press_label, 0, 0);
|
||||
|
||||
var command_page = new Views.ConfigCommand (serial_number);
|
||||
|
||||
stack = new Gtk.Stack ();
|
||||
stack.add_named (disconnected_page, "disconnected");
|
||||
stack.add_named (press_page, "keypress");
|
||||
stack.add_named (command_page, "command");
|
||||
|
||||
get_content_area ().add (stack);
|
||||
|
||||
if (Daemon.instance.get_obs_state () != "Connected") {
|
||||
add_button ("Close", Gtk.ResponseType.REJECT);
|
||||
} else {
|
||||
add_button ("Close", Gtk.ResponseType.CLOSE);
|
||||
}
|
||||
|
||||
show_all ();
|
||||
|
||||
Daemon.instance.key_press_signal.connect ((_obj, key_info) => {
|
||||
if (serial_number == key_info.serial_number) {
|
||||
this.key_info = key_info;
|
||||
command_page.key = key_info.key;
|
||||
stack.set_visible_child_name ("command");
|
||||
} else {
|
||||
Daemon.instance.key_press.begin ();
|
||||
}
|
||||
});
|
||||
|
||||
if (Daemon.instance.get_obs_state () != "Connected") {
|
||||
stack.set_visible_child_name ("disconnected");
|
||||
} else {
|
||||
stack.set_visible_child_name ("keypress");
|
||||
Daemon.instance.key_press.begin ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
102
src/MainWindow.vala
Normal file
102
src/MainWindow.vala
Normal file
|
@ -0,0 +1,102 @@
|
|||
public class Streamdeck.MainWindow : Hdy.ApplicationWindow {
|
||||
public const string ACTION_PREFIX = "win.";
|
||||
public const string ACTION_QUIT = "action_quit";
|
||||
|
||||
public Views.DeckStack deck_stack { get; private set; }
|
||||
public Gtk.Stack main_stack { get; private set; }
|
||||
|
||||
private uint configure_id;
|
||||
|
||||
private const ActionEntry[] ACTION_ENTRIES = {
|
||||
{ ACTION_QUIT, action_quit }
|
||||
};
|
||||
|
||||
public MainWindow (Gtk.Application application) {
|
||||
Object (application: application);
|
||||
|
||||
application.set_accels_for_action (
|
||||
ACTION_PREFIX + ACTION_QUIT,
|
||||
{"<Control>q", "<Control>w"}
|
||||
);
|
||||
}
|
||||
|
||||
construct {
|
||||
Hdy.init ();
|
||||
|
||||
add_action_entries (ACTION_ENTRIES, this);
|
||||
}
|
||||
|
||||
public void build_ui () {
|
||||
height_request = 350;
|
||||
width_request = 400;
|
||||
title = _("Streamdeck");
|
||||
|
||||
int window_x, window_y, window_width, window_height;
|
||||
App.saved_state.get ("window-position", "(ii)", out window_x, out window_y);
|
||||
App.saved_state.get ("window-size", "(ii)", out window_width, out window_height);
|
||||
|
||||
set_default_size (window_width, window_height);
|
||||
|
||||
if (window_x != -1 || window_y != -1) {
|
||||
move (window_x, window_y);
|
||||
}
|
||||
|
||||
if (App.saved_state.get_boolean ("window-maximized")) {
|
||||
maximize ();
|
||||
}
|
||||
|
||||
main_stack = new Gtk.Stack ();
|
||||
main_stack.add_titled (new Views.DeckView (), "deck-config", _("Streamdecks"));
|
||||
main_stack.add_titled (new Views.ObsView (), "obs-config", _("OBS"));
|
||||
|
||||
var stack_switcher = new Gtk.StackSwitcher ();
|
||||
stack_switcher.margin = 12;
|
||||
stack_switcher.halign = Gtk.Align.CENTER;
|
||||
stack_switcher.homogeneous = true;
|
||||
stack_switcher.stack = main_stack;
|
||||
|
||||
var headerbar = new Hdy.HeaderBar ();
|
||||
headerbar.show_close_button = true;
|
||||
headerbar.set_title (_("Streamdeck"));
|
||||
headerbar.show_all ();
|
||||
|
||||
var grid = new Gtk.Grid ();
|
||||
grid.attach (headerbar, 0, 0);
|
||||
grid.attach (stack_switcher, 0, 1);
|
||||
grid.attach (main_stack, 0, 2);
|
||||
grid.show_all ();
|
||||
|
||||
add (grid);
|
||||
}
|
||||
|
||||
private void action_quit () {
|
||||
destroy ();
|
||||
}
|
||||
|
||||
public void to_obs () {
|
||||
main_stack.set_visible_child_name ("obs-config");
|
||||
}
|
||||
|
||||
public override bool configure_event (Gdk.EventConfigure event) {
|
||||
if (configure_id == 0) {
|
||||
configure_id = Timeout.add (200, () => {
|
||||
configure_id = 0;
|
||||
|
||||
App.saved_state.set_boolean ("window-maximized", is_maximized);
|
||||
|
||||
if (!is_maximized) {
|
||||
int width, height, root_x, root_y;
|
||||
get_position (out root_x, out root_y);
|
||||
get_size (out width, out height);
|
||||
|
||||
App.saved_state.set ("window-position", "(ii)", root_x, root_y);
|
||||
App.saved_state.set ("window-size", "(ii)", width, height);
|
||||
}
|
||||
|
||||
return GLib.Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
return base.configure_event (event);
|
||||
}
|
||||
}
|
128
src/Views/ConfigCommand.vala
Normal file
128
src/Views/ConfigCommand.vala
Normal file
|
@ -0,0 +1,128 @@
|
|||
namespace Streamdeck.Views {
|
||||
public class ConfigCommand : Gtk.Grid {
|
||||
private Gtk.Stack command_stack;
|
||||
private Gtk.ComboBoxText scenes_combobox;
|
||||
private Widgets.CommandComboBox command_combobox;
|
||||
private string serial_number;
|
||||
|
||||
public Data.Command? command;
|
||||
public uint8? key;
|
||||
|
||||
public signal void changed (Data.Command command);
|
||||
|
||||
public ConfigCommand (string serial_number) {
|
||||
this.serial_number = serial_number;
|
||||
|
||||
build ();
|
||||
}
|
||||
|
||||
public ConfigCommand.from_existing (string serial_number, Data.Command command) {
|
||||
this.serial_number = serial_number;
|
||||
this.key = command.get_command_key ();
|
||||
this.command = command;
|
||||
|
||||
build ();
|
||||
|
||||
switch (command.get_command_type ()) {
|
||||
case Data.CommandType.SWITCH_SCENE:
|
||||
var scene_name = ((Data.SwitchScene) command).scene_name;
|
||||
scenes_combobox.set_active_id (scene_name);
|
||||
command_combobox.set_active_id ("SwitchScene");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void build () {
|
||||
var command_title = new Gtk.Label (
|
||||
_("Configure the command you wish send")
|
||||
);
|
||||
command_title.valign = Gtk.Align.CENTER;
|
||||
command_title.get_style_context ().add_class (Granite.STYLE_CLASS_H3_LABEL);
|
||||
|
||||
var command_label = new Gtk.Label (
|
||||
_("Command:")
|
||||
);
|
||||
command_label.halign = Gtk.Align.START;
|
||||
command_label.valign = Gtk.Align.CENTER;
|
||||
|
||||
command_combobox = new Widgets.CommandComboBox ();
|
||||
|
||||
var scenes_label = new Gtk.Label (_("to"));
|
||||
|
||||
scenes_combobox = new Gtk.ComboBoxText ();
|
||||
scenes_combobox.id_column = 1;
|
||||
foreach (var scene in Daemon.instance.get_scenes ()) {
|
||||
scenes_combobox.append (scene, scene);
|
||||
}
|
||||
|
||||
var switch_scene_grid = new Gtk.Grid ();
|
||||
switch_scene_grid.attach (scenes_label, 0, 0);
|
||||
switch_scene_grid.attach (scenes_combobox, 1, 0, 2);
|
||||
switch_scene_grid.column_spacing = 12;
|
||||
switch_scene_grid.row_spacing = 12;
|
||||
switch_scene_grid.halign = Gtk.Align.CENTER;
|
||||
|
||||
command_stack = new Gtk.Stack ();
|
||||
command_stack.add_named (new Gtk.Label ("..."), "Empty");
|
||||
command_stack.add_named (switch_scene_grid, "SwitchScene");
|
||||
|
||||
column_spacing = 12;
|
||||
row_spacing = 12;
|
||||
halign = Gtk.Align.CENTER;
|
||||
margin = 24;
|
||||
|
||||
attach (command_title, 0, 0, 3);
|
||||
attach (command_label, 0, 1);
|
||||
attach (command_combobox, 1, 1);
|
||||
attach (command_stack, 2, 1);
|
||||
|
||||
show_all ();
|
||||
|
||||
scenes_combobox.changed.connect (() => {
|
||||
var scene_name = scenes_combobox.get_active_text ();
|
||||
if (command != null && command.get_command_type () == Data.CommandType.SWITCH_SCENE) {
|
||||
unowned var switch_scene = (Data.SwitchScene) command;
|
||||
switch_scene.scene_name = scene_name;
|
||||
changed (command);
|
||||
} else if (key != null) {
|
||||
command = new Data.SwitchScene (key, scene_name);
|
||||
changed (command);
|
||||
}
|
||||
});
|
||||
|
||||
changed.connect ((_obj, cmd) => {
|
||||
Daemon.instance.add_command.begin (
|
||||
serial_number,
|
||||
cmd.get_command_key (),
|
||||
cmd.to_json (),
|
||||
(obj, res) => {
|
||||
try {
|
||||
Daemon.instance.add_command.end (res);
|
||||
Daemon.instance.update_command_cache (serial_number);
|
||||
} catch (Error e) {
|
||||
print ("Error saving command %s\n", e.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
command_combobox.selected.connect ((type) => {
|
||||
if (key != null) {
|
||||
handle_type_change (type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handle_type_change (Data.CommandType type) {
|
||||
switch (type) {
|
||||
case Data.CommandType.SWITCH_SCENE:
|
||||
command_stack.set_visible_child_name ("SwitchScene");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
src/Views/DeckStack.vala
Normal file
31
src/Views/DeckStack.vala
Normal file
|
@ -0,0 +1,31 @@
|
|||
namespace Streamdeck.Views {
|
||||
public class DeckStack : Gtk.Stack {
|
||||
construct {
|
||||
add_named (new Widgets.EmptyConfigPane (), "empty-state");
|
||||
|
||||
show_all();
|
||||
}
|
||||
|
||||
public void select_deck (string serial_number) {
|
||||
if (get_child_by_name (serial_number) != null) {
|
||||
set_visible_child_name (serial_number);
|
||||
}
|
||||
}
|
||||
|
||||
public void add_deck (DeckInfo deck_info) {
|
||||
if (get_child_by_name (deck_info.serial_number) != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pane = new Widgets.CommandList (deck_info);
|
||||
add_named (pane, deck_info.serial_number);
|
||||
}
|
||||
|
||||
public void remove_deck (DeckInfo deck_info) {
|
||||
var child = get_child_by_name (deck_info.serial_number);
|
||||
if (child != null) {
|
||||
remove (child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
66
src/Views/DeckView.vala
Normal file
66
src/Views/DeckView.vala
Normal file
|
@ -0,0 +1,66 @@
|
|||
namespace Streamdeck.Views {
|
||||
public class DeckView : Gtk.Grid {
|
||||
private DeckStack deck_stack;
|
||||
|
||||
construct {
|
||||
column_homogeneous = true;
|
||||
column_spacing = 12;
|
||||
row_spacing = 12;
|
||||
|
||||
deck_stack = new Views.DeckStack ();
|
||||
deck_stack.margin = 12;
|
||||
deck_stack.margin_start = 0;
|
||||
|
||||
var deck_list = new Widgets.DeckList (deck_stack);
|
||||
|
||||
foreach (DeckInfo deck_info in Daemon.instance.get_decks()) {
|
||||
deck_list.add_deck (deck_info);
|
||||
}
|
||||
|
||||
if (!deck_list.any_selected ()) {
|
||||
deck_list.select_first_item ();
|
||||
}
|
||||
|
||||
deck_list.show_all ();
|
||||
deck_stack.show_all ();
|
||||
|
||||
var scrolled_window = new Gtk.ScrolledWindow (null, null);
|
||||
scrolled_window.add (deck_list);
|
||||
scrolled_window.expand = true;
|
||||
|
||||
var sidebar = new Gtk.Grid ();
|
||||
sidebar.orientation = Gtk.Orientation.VERTICAL;
|
||||
sidebar.add(scrolled_window);
|
||||
sidebar.margin = 12;
|
||||
sidebar.margin_end = 0;
|
||||
|
||||
attach (sidebar, 0, 0);
|
||||
attach (deck_stack, 1, 0, 2, 1);
|
||||
|
||||
Daemon.instance.on_decks_added.connect ((_obj, decks) => {
|
||||
foreach (DeckInfo deck_info in decks) {
|
||||
deck_list.add_deck (deck_info);
|
||||
}
|
||||
|
||||
if (!deck_list.any_selected ()) {
|
||||
deck_list.select_first_item ();
|
||||
}
|
||||
});
|
||||
|
||||
Daemon.instance.on_decks_removed.connect ((_obj, decks) => {
|
||||
foreach (DeckInfo deck_info in decks) {
|
||||
deck_list.remove_deck (deck_info);
|
||||
}
|
||||
|
||||
if (!deck_list.any_selected ()) {
|
||||
deck_list.select_first_item ();
|
||||
}
|
||||
|
||||
deck_list.show_all ();
|
||||
});
|
||||
|
||||
show_all ();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
94
src/Views/ObsView.vala
Normal file
94
src/Views/ObsView.vala
Normal file
|
@ -0,0 +1,94 @@
|
|||
namespace Streamdeck.Views {
|
||||
public class ObsView : Gtk.Grid {
|
||||
construct {
|
||||
column_spacing = 12;
|
||||
row_spacing = 12;
|
||||
halign = Gtk.Align.CENTER;
|
||||
margin = 24;
|
||||
margin_top = 64;
|
||||
|
||||
var host_label = new Gtk.Label (_("Hostname:"));
|
||||
var host_entry = new Gtk.Entry ();
|
||||
host_entry.valign = Gtk.Align.CENTER;
|
||||
host_entry.get_style_context ().add_class (Granite.STYLE_CLASS_H3_LABEL);
|
||||
|
||||
var host_buffer = host_entry.get_buffer ();
|
||||
|
||||
var port_label = new Gtk.Label (_("Port:"));
|
||||
var port_entry = new Gtk.SpinButton.with_range (1, 65535, 1);
|
||||
port_entry.valign = Gtk.Align.CENTER;
|
||||
port_entry.snap_to_ticks = true;
|
||||
|
||||
var connect_button = new Gtk.Button ();
|
||||
switch (Daemon.instance.get_obs_state ()) {
|
||||
case "Disconnected":
|
||||
connect_button.label = _("Connect");
|
||||
break;
|
||||
default:
|
||||
connect_button.label = _("Reconnect");
|
||||
break;
|
||||
}
|
||||
|
||||
App.obs_settings.bind (
|
||||
"host",
|
||||
host_buffer,
|
||||
"text",
|
||||
GLib.SettingsBindFlags.DEFAULT
|
||||
);
|
||||
|
||||
App.obs_settings.bind (
|
||||
"port",
|
||||
port_entry,
|
||||
"value",
|
||||
GLib.SettingsBindFlags.DEFAULT
|
||||
);
|
||||
|
||||
connect_button.clicked.connect (() => {
|
||||
connect_button.sensitive = false;
|
||||
switch (Daemon.instance.get_obs_state ()) {
|
||||
case "Disconnected":
|
||||
Daemon.instance.connect_obs.begin ((_obj, res) => {
|
||||
try {
|
||||
Daemon.instance.connect_obs.end (res);
|
||||
} catch (Error e) {
|
||||
print ("Error connecting to obs: %s\n", e.message);
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
Daemon.instance.disconnect_obs.begin ((_obj, res) => {
|
||||
try {
|
||||
Daemon.instance.disconnect_obs.end (res);
|
||||
} catch (Error e) {
|
||||
print ("Error disconnecting from obs: %s\n", e.message);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
Daemon.instance.obs_state_signal.connect ((_obj, state) => {
|
||||
if (!connect_button.sensitive) {
|
||||
connect_button.sensitive = true;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case "Disconnected":
|
||||
connect_button.label = _("Connect");
|
||||
break;
|
||||
default:
|
||||
connect_button.label = _("Reconnect");
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
attach (host_label, 0, 0);
|
||||
attach (host_entry, 1, 0);
|
||||
attach (port_label, 0, 1);
|
||||
attach (port_entry, 1, 1);
|
||||
attach (connect_button, 1, 2);
|
||||
|
||||
show_all ();
|
||||
}
|
||||
}
|
||||
}
|
32
src/Widgets/CommandComboBox.vala
Normal file
32
src/Widgets/CommandComboBox.vala
Normal file
|
@ -0,0 +1,32 @@
|
|||
namespace Streamdeck.Widgets {
|
||||
public class CommandComboBox : Gtk.ComboBoxText {
|
||||
private Data.CommandType? _selected;
|
||||
|
||||
public signal void selected (Data.CommandType type);
|
||||
|
||||
construct {
|
||||
id_column = 1;
|
||||
|
||||
foreach (Data.CommandDescription cmd in Data.Command.available ()) {
|
||||
append (cmd.id, cmd.name);
|
||||
}
|
||||
|
||||
changed.connect ((_obj) => {
|
||||
var id = get_active_id ();
|
||||
var type = Data.Command.type_string (id);
|
||||
|
||||
if (type != null) {
|
||||
selected (type);
|
||||
}
|
||||
|
||||
_selected = type;
|
||||
});
|
||||
|
||||
show_all ();
|
||||
}
|
||||
|
||||
public Data.CommandType? get_selected_type () {
|
||||
return _selected;
|
||||
}
|
||||
}
|
||||
}
|
136
src/Widgets/CommandList.vala
Normal file
136
src/Widgets/CommandList.vala
Normal file
|
@ -0,0 +1,136 @@
|
|||
namespace Streamdeck.Widgets {
|
||||
public class CommandList : Gtk.Frame {
|
||||
private DeckInfo info;
|
||||
private Gtk.ListBox list_box;
|
||||
private Dialogs.NewCommandDialog? new_command_dialog;
|
||||
private Gee.HashMap<uint8, CommandRow> row_map = new Gee.HashMap<uint8, CommandRow> ();
|
||||
|
||||
public CommandList(DeckInfo info) {
|
||||
this.info = info;
|
||||
|
||||
var alert = new Granite.Widgets.AlertView (
|
||||
_("No commands registered"),
|
||||
_("Try adding a new command."),
|
||||
""
|
||||
);
|
||||
alert.show_all ();
|
||||
|
||||
list_box = new Gtk.ListBox ();
|
||||
list_box.selection_mode = Gtk.SelectionMode.SINGLE;
|
||||
list_box.activate_on_single_click = true;
|
||||
list_box.set_placeholder (alert);
|
||||
|
||||
var add_button = new Gtk.Button.from_icon_name (
|
||||
"list-add-symbolic",
|
||||
Gtk.IconSize.BUTTON
|
||||
);
|
||||
add_button.tooltip_text = _("Add");
|
||||
|
||||
var remove_button = new Gtk.Button.from_icon_name (
|
||||
"list-remove-symbolic",
|
||||
Gtk.IconSize.BUTTON
|
||||
);
|
||||
remove_button.tooltip_text = _("Remove");
|
||||
|
||||
var actionbar = new Gtk.ActionBar ();
|
||||
actionbar.get_style_context ().add_class (Gtk.STYLE_CLASS_INLINE_TOOLBAR);
|
||||
actionbar.add (add_button);
|
||||
actionbar.add (remove_button);
|
||||
actionbar.show_all ();
|
||||
|
||||
var scrolled = new Gtk.ScrolledWindow (null, null);
|
||||
scrolled.expand = true;
|
||||
scrolled.add (list_box);
|
||||
|
||||
var commands = Daemon.instance.get_commands (info.serial_number);
|
||||
if (commands != null) {
|
||||
foreach (CommandInfo cmd_info in commands) {
|
||||
var command = Data.Command.parse (cmd_info.key, cmd_info.command);
|
||||
|
||||
if (command != null) {
|
||||
var existing = row_map.get (cmd_info.key);
|
||||
if (existing != null) {
|
||||
list_box.remove (existing);
|
||||
}
|
||||
|
||||
var row = new CommandRow (info.serial_number, command);
|
||||
row_map.set (cmd_info.key, row);
|
||||
list_box.add (row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var grid = new Gtk.Grid ();
|
||||
|
||||
grid.attach (scrolled, 0, 0);
|
||||
grid.attach (actionbar, 0, 1);
|
||||
|
||||
add (grid);
|
||||
|
||||
remove_button.clicked.connect (() => {
|
||||
var row = list_box.get_selected_row ();
|
||||
if (row == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
unowned var command_row = (CommandRow) row;
|
||||
var key = command_row.get_key ();
|
||||
|
||||
Daemon.instance.remove_command.begin (info.serial_number, key, (_obj, res) => {
|
||||
try {
|
||||
Daemon.instance.remove_command.end (res);
|
||||
|
||||
row_map.unset (key);
|
||||
list_box.remove (row);
|
||||
} catch (Error e) {
|
||||
print ("Error removing command: %s\n", e.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
add_button.clicked.connect (() => {
|
||||
if (new_command_dialog == null) {
|
||||
new_command_dialog = new Dialogs.NewCommandDialog (info.serial_number);
|
||||
new_command_dialog.transient_for = (Gtk.Window) get_toplevel ();
|
||||
new_command_dialog.show_all ();
|
||||
|
||||
new_command_dialog.response.connect ((response_id) => {
|
||||
if (response_id == Gtk.ResponseType.REJECT) {
|
||||
unowned var app = (MainWindow) get_toplevel ();
|
||||
app.to_obs ();
|
||||
}
|
||||
|
||||
new_command_dialog.destroy ();
|
||||
});
|
||||
|
||||
new_command_dialog.destroy.connect (() => {
|
||||
new_command_dialog = null;
|
||||
});
|
||||
}
|
||||
|
||||
new_command_dialog.present ();
|
||||
});
|
||||
|
||||
Daemon.instance.commands_signal.connect ((_obj, serial_number, commands) => {
|
||||
if (serial_number == info.serial_number) {
|
||||
foreach (CommandInfo cmd_info in commands) {
|
||||
var command = Data.Command.parse (cmd_info.key, cmd_info.command);
|
||||
|
||||
if (command != null) {
|
||||
var existing = row_map.get (cmd_info.key);
|
||||
if (existing != null) {
|
||||
list_box.remove (existing);
|
||||
}
|
||||
|
||||
var row = new CommandRow (serial_number, command);
|
||||
row_map.set (cmd_info.key, row);
|
||||
list_box.add (row);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
show_all ();
|
||||
}
|
||||
}
|
||||
}
|
100
src/Widgets/CommandRow.vala
Normal file
100
src/Widgets/CommandRow.vala
Normal file
|
@ -0,0 +1,100 @@
|
|||
namespace Streamdeck.Widgets {
|
||||
public class CommandRow : Gtk.ListBoxRow {
|
||||
private string serial_number;
|
||||
private Data.Command command;
|
||||
private Dialogs.EditCommandDialog? edit_dialog;
|
||||
|
||||
public CommandRow (string serial_number, Data.Command command) {
|
||||
this.serial_number = serial_number;
|
||||
this.command = command;
|
||||
|
||||
var key = command.get_command_key ();
|
||||
var row_key = new Gtk.Label (@"$(key)");
|
||||
row_key.halign = Gtk.Align.START;
|
||||
row_key.valign = Gtk.Align.START;
|
||||
row_key.show_all ();
|
||||
|
||||
var row_command = new Gtk.Label (null);
|
||||
row_command.halign = Gtk.Align.START;
|
||||
row_command.valign = Gtk.Align.START;
|
||||
row_command.show_all ();
|
||||
|
||||
var row_grid = new Gtk.Grid ();
|
||||
row_grid.orientation = Gtk.Orientation.HORIZONTAL;
|
||||
row_grid.margin = 6;
|
||||
row_grid.margin_start = 3;
|
||||
row_grid.column_spacing = 3;
|
||||
row_grid.add (row_key);
|
||||
row_grid.add (row_command);
|
||||
|
||||
switch (command.get_command_type ()) {
|
||||
case Data.CommandType.SWITCH_SCENE:
|
||||
row_command.label = _("Switch scene");
|
||||
switch_scene_ui (row_grid);
|
||||
break;
|
||||
default:
|
||||
row_command.label = _("Unknown Command");
|
||||
break;
|
||||
}
|
||||
|
||||
var row_edit = new Gtk.Button.from_icon_name (
|
||||
"document-edit-symbolic",
|
||||
Gtk.IconSize.BUTTON
|
||||
);
|
||||
row_edit.tooltip_text = _("Edit");
|
||||
row_edit.halign = Gtk.Align.END;
|
||||
row_edit.valign = Gtk.Align.END;
|
||||
row_edit.expand = true;
|
||||
row_edit.clicked.connect (() => {
|
||||
if (edit_dialog == null) {
|
||||
edit_dialog = new Dialogs.EditCommandDialog (serial_number, command);
|
||||
edit_dialog.transient_for = (Gtk.Window) get_toplevel ();
|
||||
edit_dialog.show_all ();
|
||||
|
||||
edit_dialog.response.connect ((response_id) => {
|
||||
if (response_id == Gtk.ResponseType.REJECT) {
|
||||
unowned var app = (MainWindow) get_toplevel ();
|
||||
app.to_obs ();
|
||||
}
|
||||
|
||||
edit_dialog.destroy ();
|
||||
});
|
||||
|
||||
edit_dialog.destroy.connect (() => {
|
||||
edit_dialog = null;
|
||||
});
|
||||
}
|
||||
|
||||
edit_dialog.present ();
|
||||
});
|
||||
|
||||
row_grid.add (row_edit);
|
||||
|
||||
row_grid.show_all();
|
||||
|
||||
add (row_grid);
|
||||
|
||||
show_all ();
|
||||
}
|
||||
|
||||
public uint8 get_key () {
|
||||
return command.get_command_key ();
|
||||
}
|
||||
|
||||
private void switch_scene_ui (Gtk.Grid row_grid) {
|
||||
unowned var switch_scene = (Data.SwitchScene) command;
|
||||
var to = new Gtk.Label (_("to"));
|
||||
to.halign = Gtk.Align.START;
|
||||
to.valign = Gtk.Align.START;
|
||||
to.show_all ();
|
||||
|
||||
var scene_name = new Gtk.Label (switch_scene.scene_name);
|
||||
scene_name.halign = Gtk.Align.START;
|
||||
scene_name.valign = Gtk.Align.START;
|
||||
scene_name.show_all ();
|
||||
|
||||
row_grid.add (to);
|
||||
row_grid.add (scene_name);
|
||||
}
|
||||
}
|
||||
}
|
47
src/Widgets/DeckItem.vala
Normal file
47
src/Widgets/DeckItem.vala
Normal file
|
@ -0,0 +1,47 @@
|
|||
namespace Streamdeck.Widgets {
|
||||
public class DeckItem : Gtk.ListBoxRow {
|
||||
public string serial_number { get; set; }
|
||||
public string device_name { get; set; }
|
||||
public string port_name { get; set; }
|
||||
|
||||
public DeckItem (string serial_number, string device_name, string port_name) {
|
||||
Object (
|
||||
serial_number: serial_number,
|
||||
device_name: device_name,
|
||||
port_name: port_name
|
||||
);
|
||||
}
|
||||
|
||||
construct {
|
||||
var row_title = new Gtk.Label (device_name);
|
||||
row_title.get_style_context ().add_class (Granite.STYLE_CLASS_H3_LABEL);
|
||||
row_title.halign = Gtk.Align.START;
|
||||
row_title.valign = Gtk.Align.START;
|
||||
row_title.ellipsize = Pango.EllipsizeMode.END;
|
||||
row_title.show_all ();
|
||||
|
||||
var row_description = new Gtk.Label (port_name);
|
||||
row_description.margin_top = 2;
|
||||
row_description.use_markup = true;
|
||||
row_description.halign = Gtk.Align.START;
|
||||
row_description.valign = Gtk.Align.START;
|
||||
row_description.ellipsize = Pango.EllipsizeMode.END;
|
||||
row_description.show_all ();
|
||||
|
||||
var row_grid = new Gtk.Grid ();
|
||||
row_grid.margin = 6;
|
||||
row_grid.margin_start = 3;
|
||||
row_grid.column_spacing = 3;
|
||||
row_grid.attach (row_title, 0, 0);
|
||||
row_grid.attach (row_description, 0, 1);
|
||||
row_grid.show_all ();
|
||||
|
||||
add (row_grid);
|
||||
|
||||
bind_property ("device-name", row_title, "label");
|
||||
bind_property ("port-name", row_description, "label");
|
||||
|
||||
show_all ();
|
||||
}
|
||||
}
|
||||
}
|
110
src/Widgets/DeckList.vala
Normal file
110
src/Widgets/DeckList.vala
Normal file
|
@ -0,0 +1,110 @@
|
|||
namespace Streamdeck.Widgets {
|
||||
public class DeckList : Gtk.Frame {
|
||||
private Gtk.Label usb_l;
|
||||
private Views.DeckStack deck_stack;
|
||||
private Gtk.ListBox list_box;
|
||||
|
||||
public DeckList (Views.DeckStack deck_stack) {
|
||||
this.deck_stack = deck_stack;
|
||||
}
|
||||
|
||||
construct {
|
||||
var alert = new Granite.Widgets.AlertView (
|
||||
_("No streamdecks found"),
|
||||
_("Try plugging one in!"),
|
||||
""
|
||||
);
|
||||
alert.show_all();
|
||||
|
||||
list_box = new Gtk.ListBox ();
|
||||
list_box.selection_mode = Gtk.SelectionMode.SINGLE;
|
||||
list_box.activate_on_single_click = true;
|
||||
list_box.set_placeholder (alert);
|
||||
|
||||
usb_l = new Gtk.Label (_("Usb"));
|
||||
usb_l.get_style_context ().add_class (Granite.STYLE_CLASS_H4_LABEL);
|
||||
usb_l.halign = Gtk.Align.START;
|
||||
|
||||
list_box.set_header_func (update_headers);
|
||||
|
||||
list_box.row_selected.connect ((row) => {
|
||||
row.activate ();
|
||||
unowned var item = (DeckItem) row;
|
||||
deck_stack.select_deck (item.serial_number);
|
||||
});
|
||||
|
||||
var scroll = new Gtk.ScrolledWindow (null, null);
|
||||
scroll.hscrollbar_policy = Gtk.PolicyType.NEVER;
|
||||
scroll.expand = true;
|
||||
scroll.add (list_box);
|
||||
|
||||
add (scroll);
|
||||
|
||||
show_all ();
|
||||
}
|
||||
|
||||
public void add_deck (DeckInfo deck_info) {
|
||||
var already_present = false;
|
||||
|
||||
foreach (Gtk.Widget child in list_box.get_children ()) {
|
||||
var deck_item = (DeckItem) child;
|
||||
|
||||
already_present = already_present
|
||||
|| deck_item.serial_number == deck_info.serial_number;
|
||||
}
|
||||
|
||||
if (!already_present) {
|
||||
var item = new DeckItem (
|
||||
deck_info.serial_number,
|
||||
deck_info.device_name,
|
||||
deck_info.port_name
|
||||
);
|
||||
|
||||
deck_stack.add_deck (deck_info);
|
||||
list_box.add (item);
|
||||
}
|
||||
|
||||
deck_stack.show_all ();
|
||||
show_all ();
|
||||
}
|
||||
|
||||
public void remove_deck (DeckInfo deck_info) {
|
||||
foreach (Gtk.Widget child in list_box.get_children ()) {
|
||||
var deck_item = (DeckItem) child;
|
||||
|
||||
if (deck_item.serial_number == deck_info.serial_number) {
|
||||
deck_stack.remove_deck (deck_info);
|
||||
list_box.remove (deck_item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
deck_stack.show_all ();
|
||||
show_all ();
|
||||
}
|
||||
|
||||
private void update_headers (Gtk.ListBoxRow row, Gtk.ListBoxRow? before = null) {
|
||||
if (before != null) {
|
||||
row.set_header (null);
|
||||
}
|
||||
|
||||
if (usb_l.get_parent () != null) {
|
||||
usb_l.unparent ();
|
||||
}
|
||||
|
||||
row.set_header (usb_l);
|
||||
}
|
||||
|
||||
public bool any_selected () {
|
||||
return list_box.get_selected_row () != null;
|
||||
}
|
||||
|
||||
public void select_first_item () {
|
||||
var row = list_box.get_row_at_index (0);
|
||||
if (row == null) {
|
||||
return;
|
||||
}
|
||||
list_box.select_row (row);
|
||||
}
|
||||
}
|
||||
}
|
20
src/Widgets/DisconnectedPage.vala
Normal file
20
src/Widgets/DisconnectedPage.vala
Normal file
|
@ -0,0 +1,20 @@
|
|||
namespace Streamdeck.Widgets {
|
||||
public class DisconnectedPage : Gtk.Grid {
|
||||
construct {
|
||||
var disconnected_label = new Gtk.Label (
|
||||
_("OBS is currently disconnected, try connecting it before configuring commands")
|
||||
);
|
||||
disconnected_label.valign = Gtk.Align.CENTER;
|
||||
disconnected_label.get_style_context ().add_class (Granite.STYLE_CLASS_H3_LABEL);
|
||||
|
||||
column_spacing = 12;
|
||||
row_spacing = 12;
|
||||
halign = Gtk.Align.CENTER;
|
||||
margin = 24;
|
||||
|
||||
attach (disconnected_label, 0, 0);
|
||||
|
||||
show_all ();
|
||||
}
|
||||
}
|
||||
}
|
21
src/Widgets/EmptyConfigPane.vala
Normal file
21
src/Widgets/EmptyConfigPane.vala
Normal file
|
@ -0,0 +1,21 @@
|
|||
namespace Streamdeck.Widgets {
|
||||
public class EmptyConfigPane: Gtk.Grid {
|
||||
construct {
|
||||
column_spacing = 12;
|
||||
row_spacing = 12;
|
||||
halign = Gtk.Align.CENTER;
|
||||
margin = 24;
|
||||
margin_top = 64;
|
||||
|
||||
var label = new Gtk.Label (
|
||||
_("No streamdeck selected")
|
||||
);
|
||||
label.valign = Gtk.Align.CENTER;
|
||||
label.get_style_context ().add_class (Granite.STYLE_CLASS_H3_LABEL);
|
||||
|
||||
attach (label, 0, 0);
|
||||
|
||||
show_all ();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue